# HG changeset patch # User Zerotorescue # Date 1286464663 -7200 # Node ID c6ff7ba0e8f66fe4744838f941449a43c2362195 Reasonably functional now. Cleaning up some stuff which might have to be reverted. diff -r 000000000000 -r c6ff7ba0e8f6 .pkgmeta --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.pkgmeta Thu Oct 07 17:17:43 2010 +0200 @@ -0,0 +1,47 @@ +externals: + Libs/AceAddon-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceAddon-3.0 + tag: latest + Libs/AceConfig-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConfig-3.0 + tag: latest + Libs/AceDB-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDB-3.0 + tag: latest + Libs/AceDBOptions-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDBOptions-3.0 + tag: latest + Libs/AceEvent-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceEvent-3.0 + tag: latest + Libs/AceGUI-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceGUI-3.0 + tag: latest + Libs/AceGUI-3.0-SharedMediaWidgets: + url: svn://svn.wowace.com/wow/ace-gui-3-0-shared-media-widgets/mainline/trunk/AceGUI-3.0-SharedMediaWidgets + tag: latest + Libs/AceHook-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceHook-3.0 + tag: latest + Libs/AceLocale-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceLocale-3.0 + tag: latest + Libs/AceSerializer-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceSerializer-3.0 + tag: latest + Libs/AceTimer-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceTimer-3.0 + tag: latest + Libs/CallbackHandler-1.0: + url: svn://svn.wowace.com/wow/callbackhandler/mainline/trunk/CallbackHandler-1.0 + tag: latest + Libs/LibSharedMedia-3.0: + url: svn://svn.wowace.com/wow/libsharedmedia-3-0/mainline/trunk/LibSharedMedia-3.0 + tag: latest + Libs/LibStub: + url: svn://svn.wowace.com/wow/libstub/mainline/trunk + tag: latest + +enable-nolib-creation: no + +manual-changelog: Changelog.txt diff -r 000000000000 -r c6ff7ba0e8f6 Changelog.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Changelog.txt Thu Oct 07 17:17:43 2010 +0200 @@ -0,0 +1,10 @@ +v1.0.0 Zerotorescue ? + + Initial release. + +------------------------------------------------------------ +Legend: + +R Removed +A Added +M Modified diff -r 000000000000 -r c6ff7ba0e8f6 Core.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core.lua Thu Oct 07 17:17:43 2010 +0200 @@ -0,0 +1,1306 @@ +-- You can access this addon's object through: LibStub("AceAddon-3.0"):GetAddon("Inventory") +local addon = LibStub("AceAddon-3.0"):NewAddon("Inventory", "AceEvent-3.0", "AceTimer-3.0"); + +local Media = LibStub("LibSharedMedia-3.0"); +Media:Register("sound", "Cartoon FX", [[Sound\Doodad\Goblin_Lottery_Open03.wav]]); +Media:Register("sound", "Cheer", [[Sound\Event Sounds\OgreEventCheerUnique.wav]]); +Media:Register("sound", "Explosion", [[Sound\Doodad\Hellfire_Raid_FX_Explosion05.wav]]); +Media:Register("sound", "Fel Nova", [[Sound\Spells\SeepingGaseous_Fel_Nova.wav]]); +Media:Register("sound", "Fel Portal", [[Sound\Spells\Sunwell_Fel_PortalStand.wav]]); +Media:Register("sound", "Magic Click", [[Sound\interface\MagicClick.wav]]); +Media:Register("sound", "Rubber Ducky", [[Sound\Doodad\Goblin_Lottery_Open01.wav]]); +Media:Register("sound", "Shing!", [[Sound\Doodad\PortcullisActive_Closed.wav]]); +Media:Register("sound", "Simon Chime", [[Sound\Doodad\SimonGame_LargeBlueTree.wav]]); +Media:Register("sound", "Simon Error", [[Sound\Spells\SimonGame_Visual_BadPress.wav]]); +Media:Register("sound", "Simon Start", [[Sound\Spells\SimonGame_Visual_GameStart.wav]]); +Media:Register("sound", "War Drums", [[Sound\Event Sounds\Event_wardrum_ogre.wav]]); +Media:Register("sound", "Wham!", [[Sound\Doodad\PVP_Lordaeron_Door_Open.wav]]); +Media:Register("sound", "Whisper Ping", [[Sound\interface\iTellMessage.wav]]); +Media:Register("sound", "You Will Die!", [[Sound\Creature\CThun\CThunYouWillDIe.wav]]); + +local AceConfigDialog, AceConfigRegistry, AceSerializer; +local groupIdToName = {}; +local options = {}; + +function addon:OnInitialize() + self:Debug("OnInitialize"); + + -- SAVED VARIABLES + + local defaults = { + global = { + groups = {}, + defaults = { + minimumStock = 60, + alertBelowMinimum = true, + summaryThresholdShow = 10, + restockTarget = 60, + minCraftingQueue = 0.05, + bonusQueue = 0.1, + priceThreshold = 0, + trackAtCharacters = {}, + colors = { + red = 0; + orange = 0.3; + yellow = 0.6; + green = 0.95; + }, + }, + }, + factionrealm = { + characters = {}, + }, + }; + + -- Register our saved variables database + self.db = LibStub("AceDB-3.0"):New("InventoryDB", defaults, true); + + -- SLASH COMMANDS + + -- Disable the AddonLoader slash commands + SLASH_INVENTORY1 = nil; + SLASH_IY1 = nil; + + -- Register our own slash commands + SLASH_INVENTORY1 = "/inventory"; + SLASH_INVENTORY2 = "/iy"; + SlashCmdList["INVENTORY"] = function(msg) + self:CommandHandler(msg); + end + + -- INTERFACE OPTIONS + + -- Attempt to remove the interface options added by AddonLoader (if enabled) + if AddonLoader and AddonLoader.RemoveInterfaceOptions then + AddonLoader:RemoveInterfaceOptions("Inventory"); + end + + -- Now create our own options frame + local frame = CreateFrame("Frame", nil, UIParent); + frame:Hide(); + frame.name = "Inventory"; + frame:HookScript("OnShow", function(self) + -- Refresh the frame to instantly show the right options + InterfaceOptionsFrame_OpenToCategory(self.name) + end); + -- And add it to the interface options + InterfaceOptions_AddCategory(frame); + + self:MakeWidgets(); + + -- Remember this character is mine + local playerName = UnitName("player"); + if not self.db.factionrealm.characters[playerName] then + self.db.factionrealm.characters[playerName] = true; + + -- Default to tracking on all chars, untracking is a convenience, not tracking by default would probably get multiple issue reports. + self.db.global.defaults.trackAtCharacters[playerName] = true; + end +end + +function addon:MakeWidgets() + local AceGUI = LibStub("AceGUI-3.0"); + + --[[ + [ ItemLinkButton ] + UserData: itemId, onClick event + OnEnter: tooltip show + OnLeave: tooltip hid + + ]] + + -- Define out custom item link button widget + -- This will be called as if it's an input element, we overwrite some of the related functions which are called for default input fields + + local widgetType = "ItemLinkButton"; + local widgetVersion = 1; + + -- Empty function for disabling functions + local function Dummy() end + + -- Overwrite the SetText-function of our custom widgets + local function CustomWidgetSetText(self, value, ...) + if value then + -- Remember the itemId in a local parameter (using :GetText, we'd have to run a pattern over it and it would all get silly) + self.itemId = tonumber(value); + + if not self.itemId then error("itemId is not a number."); end + + -- Put the icon in front of it + self:SetImage(GetItemIcon(self.itemId)); + -- Standardize the size + self:SetImageSize(16, 16); + + -- Make readable font + self:SetFontObject(GameFontHighlight); + + -- We don't want to set the itemId as text, but rather the item link, so get that. + value = select(2, GetItemInfo(value)) or ("Unknown (#%d)"):format(value); + + return self.originalSetText(self, value, ...); + end + end + + -- Overwrite the OnEnter-event of our custom widgets to use our own function + local function CustomWidgetOnEnter(self) + if self.itemId then + GameTooltip:SetOwner(self.frame, "ANCHOR_TOPRIGHT"); + GameTooltip:SetHyperlink(("item:%d"):format(self.itemId)); + GameTooltip:Show(); + end + end + + -- Overwrite the OnClick-event of our custom widgets to use our own function + local function CustomWidgetOnClick(self) + local info = self:GetUserDataTable() + + info.option.set(self, info); + end + + -- Overwrite the SetCallback-function of our custom widgets + local function CustomWidgetSetCallback(self, event, func, ...) + if event == "OnEnter" then + -- This event is called once when creating the widget + -- When it becomes hidden, all events appear to be removed, but once it's shown again, OnEnter will be re-applied + -- Hence any registered Callback must be done in here + + self.originalSetCallBack(self, "OnClick", CustomWidgetOnClick); + + return self.originalSetCallBack(self, event, CustomWidgetOnEnter, ...); + else + return self.originalSetCallBack(self, event, func, ...); + end + end + + local function CustomWidgetHideTooltip() + GameTooltip:Hide(); + end + + -- Makes an instance of our ItemLinkButton widget + local function GetItemLinkButton() + local widget = AceGUI:Create("InteractiveLabel"); + widget.type = widgetType; + + -- We can only provide custom widgets for input, select and multiselect fields + -- Input being the simplest, we use that - however, it provides two parameters: label and text. We only need one, disable the other. + widget.SetLabel = Dummy; + + if not widget.originalSetText then + -- When setting text we provide an itemId + -- Use this itemId to set the icon and do all fancy stuff + widget.originalSetText = widget.SetText; + widget.SetText = CustomWidgetSetText; + end + + + if not widget.originalSetCallBack then + -- We don't want AceConfig to overwrite our OnEnter with the tooltip functions, so override that + widget.originalSetCallBack = widget.SetCallback; + widget.SetCallback = CustomWidgetSetCallback; + + -- Make sure it's called (AceConfig will probably repeat this, but we are prepared in case it's ever changed) + widget:SetCallback("OnEnter", Dummy); + widget:SetCallback("OnLeave", CustomWidgetHideTooltip); + end + + return widget; + end + + AceGUI:RegisterWidgetType(widgetType, GetItemLinkButton, widgetVersion); +end + +function addon:CommandHandler(message) + local cmd, arg = string.split(" ", (message or ""), 2); + cmd = string.lower(cmd); + + if cmd == "c" or cmd == "config" or cmd == "conf" or cmd == "option" or cmd == "options" or cmd == "opt" or cmd == "setting" or cmd == "settings" then + self:Show(); + elseif cmd == "d" or cmd == "debug" then + self.debugChannel = false; + for i = 1, NUM_CHAT_WINDOWS do + local name = GetChatWindowInfo(i); + + if name:upper() == "DEBUG" then + self.debugChannel = _G["ChatFrame" .. i]; + + print("A debug channel already exists, used the old one. (" .. i .. ")"); + return; + end + end + + if not self.debugChannel then + -- Create a new debug channel + local chatFrame = FCF_OpenNewWindow('Debug'); + ChatFrame_RemoveAllMessageGroups(chatFrame); + self.debugChannel = chatFrame; + + print("New debug channel created."); + end + else + print("Wrong command, available: /inventory config (or /inventory c)"); + end +end + +function addon:Load() + if not AceConfigDialog and not AceConfigRegistry then + self:FillOptions(); + + -- Build options dialog + AceConfigDialog = LibStub("AceConfigDialog-3.0"); + AceConfigRegistry = LibStub("AceConfigRegistry-3.0"); + -- Register options table + LibStub("AceConfig-3.0"):RegisterOptionsTable("InventoryOptions", options); + -- Set a nice default size (so that 4 normal sized elements fit next to eachother) + AceConfigDialog:SetDefaultSize("InventoryOptions", 975, 600); + + -- In case the addon is loaded from another condition, always call the remove interface options + if AddonLoader and AddonLoader.RemoveInterfaceOptions then + AddonLoader:RemoveInterfaceOptions("Inventory"); + end + + -- Add to the blizzard addons options thing + --AceConfigDialog:AddToBlizOptions("InventoryOptions"); + end +end + +function addon:Show() + self:Load(); + + AceConfigDialog:Open("InventoryOptions"); +end + +function addon:FillOptions() + options = { + type = "group", + name = "Inventory", + childGroups = "tree", + args = { + }, + }; + + self:FillGeneralOptions(); + + options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db, true); + options.args.profiles.order = 200; + + self:MakeGroupOptions(); + + self:FillGroupOptions(); +end + +function addon:FillGeneralOptions() + options.args.general = { + order = 100, + type = "group", + name = "General", + desc = "Change general Inventory settings.", + args = { + general = { + order = 0, + type = "group", + inline = true, + name = "General", + args = { + description = { + order = 0, + type = "description", + name = "Change general settings unrelated to groups.", + }, + header = { + order = 5, + type = "header", + name = "", + }, + auctionAddon = { + order = 10, + type = "select", + name = "Prefered pricing addon", + values = { + Auctioneer = "Auctioneer", + Auctionator = "Auctionator", + }, + get = function() end, + set = function(i, v) end, + }, + itemCountAddon = { + order = 20, + type = "select", + name = "Prefered item count addon", + values = { + Altoholic = "Altoholic", + DataStore = "DataStore", + ItemCount = "ItemCount", + }, + get = function() end, + set = function(i, v) end, + }, + }, + }, + minimumStock = { + order = 10, + type = "group", + inline = true, + name = "Minimum stock", + args = { + description = { + order = 0, + type = "description", + name = "Here you can specify the default minimum amount of items you wish to keep in stock and related settings. The settings entered here will be used when you choose not to override the settings within an individual group.", + }, + header = { + order = 5, + type = "header", + name = "", + }, + minimumStock = { + order = 10, + type = "range", + min = 0, + max = 100000, + softMax = 1000, + step = 1, + name = "Minimum stock", + desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.", + get = function() return self.db.global.defaults.minimumStock; end, + set = function(i, v) self.db.global.defaults.minimumStock = v; end, + }, + summaryThresholdShow = { + order = 20, + type = "range", + min = 0, + max = 100, + softMax = 10, + step = 0.05, + isPercent = true, + name = "Show in summary when below", + desc = "Show items in the summary when below this percentage of the minimum stock.\n\nYou can manually enter a value between 1.000% and 10.000% in the edit box if the provided range is insufficient.", + get = function() return self.db.global.defaults.summaryThresholdShow; end, + set = function(i, v) self.db.global.defaults.summaryThresholdShow = v; end, + }, + alertBelowMinimum = { + order = 30, + type = "toggle", + name = "Alert when below minimum", + desc = "Show an alert when this item gets below this threshold.", + get = function() return self.db.global.defaults.alertBelowMinimum; end, + set = function(i, v) self.db.global.defaults.alertBelowMinimum = v; end, + }, + trackAtCharacters = { + order = 40, + type = "multiselect", + control = "Dropdown", -- this is not standard, normal multiselect control gives us a list of all chars with toggle-boxes. UGLY! We want a multiselect-box instead. + name = "Track at", + desc = "Select at which characters this should appear in the summary and generate alerts.", + values = function() + local temp = {}; + for charName in pairs(self.db.factionrealm.characters) do + temp[charName] = charName; + end + + return temp; + end, + get = function(i, v) + return self.db.global.defaults.trackAtCharacters[v]; + end, + set = function(i, v, e) + self.db.global.defaults.trackAtCharacters[v] = e or nil; + end, + }, + }, + }, + refill = { + order = 20, + type = "group", + inline = true, + name = "Replenishing stock", + args = { + description = { + order = 0, + type = "description", + name = function() + local r = "Here you can specify the default amount of items to which you wish to restock when you are collecting new items. This may be higher than the minimum stock. The settings entered here will be used when you choose not to override the settings within an individual group.\n\n"; + + r = r .. "When restocking the target amount is |cfffed000" .. self.db.global.defaults.restockTarget .. "|r of every item. Not queueing craftable items when only missing |cfffed000" .. floor( self.db.global.defaults.minCraftingQueue * self.db.global.defaults.restockTarget ) .. "|r (|cfffed000" .. ( self.db.global.defaults.minCraftingQueue * 100 ) .. "%|r) of the restock target."; + + return r; + end, + }, + header = { + order = 5, + type = "header", + name = "", + }, + restockTarget = { + order = 10, + type = "range", + min = 0, + max = 100000, + softMax = 1000, + step = 1, + name = "Restock target", + desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.", + get = function() return self.db.global.defaults.restockTarget; end, + set = function(i, v) self.db.global.defaults.restockTarget = v; end, + }, + minCraftingQueue = { + order = 20, + type = "range", + min = 0, + max = 1, + step = 0.01, -- 1% + isPercent = true, + name = "Don't queue if I only miss", + desc = "Don't add a craftable item to the queue if I only miss this much or less of the restock target.\n\nExample: if your restock target is set to 60 and this is set to 5%, an item won't be queued unless you are missing more than 3 of it.", + get = function() return self.db.global.defaults.minCraftingQueue; end, + set = function(i, v) self.db.global.defaults.minCraftingQueue = v; end, + }, + bonusQueue = { + order = 30, + type = "range", + min = 0, + max = 10, -- 1000% + step = 0.01, -- 1% + isPercent = true, + name = "Bonus queue", + desc = "Get additional items when I have none left.\n\nExample: if your restock target is set to 60 and this is set to 10%, you will get 66 items instead of just 60 if you end up with none left.", + get = function() return self.db.global.defaults.bonusQueue; end, + set = function(i, v) self.db.global.defaults.bonusQueue = v; end, + }, + priceThreshold = { + order = 40, + type = "input", + name = "Price threshold", + desc = "Only queue craftable items when they are worth at least this much according to your auction house addon.", + validate = function() + -- gold check + end, + get = function() return self.db.global.defaults.priceThreshold; end, + set = function(i, v) self.db.global.defaults.priceThreshold = v; end, + }, + hideFromSummaryPriceThreshold = { + order = 50, + type = "toggle", + name = "Don't show when below threshold", + desc = "Hide items from the summary when their value is below the set price threshold.", + }, + }, + }, + colorCodes = { + order = 30, + type = "group", + inline = true, + name = "Color codes", + args = { + description = { + order = 0, + type = "description", + name = "Change the color code thresholds based on the current stock remaining of the required minimum stock.", + }, + header = { + order = 5, + type = "header", + name = "", + }, + green = { + order = 10, + type = "range", + min = 0, + max = 1, + step = 0.01, + isPercent = true, + name = "|cff00ff00Green|r", + desc = "Show quantity in green when at least this much of the minimum stock is available.", + get = function() return self.db.global.defaults.colors.green; end, + set = function(i, v) self.db.global.defaults.colors.green = v; end, + }, + yellow = { + order = 20, + type = "range", + min = 0, + max = 1, + step = 0.01, + isPercent = true, + name = "|cffffff00Yellow|r", + desc = "Show quantity in yellow when at least this much of the minimum stock is available.", + get = function() return self.db.global.defaults.colors.yellow; end, + set = function(i, v) self.db.global.defaults.colors.yellow = v; end, + }, + orange = { + order = 30, + type = "range", + min = 0, + max = 1, + step = 0.01, + isPercent = true, + name = "|cffff9933Orange|r", + desc = "Show quantity in orange when at least this much of the minimum stock is available.", + get = function() return self.db.global.defaults.colors.orange; end, + set = function(i, v) self.db.global.defaults.colors.orange = v; end, + }, + red = { + order = 40, + type = "range", + min = 0, + max = 1, + step = 0.01, + isPercent = true, + name = "|cffff0000Red|r", + desc = "Show quantity in red when at least this much of the minimum stock is available.", + get = function() return self.db.global.defaults.colors.red; end, + set = function(i, v) self.db.global.defaults.colors.red = v; end, + }, + }, + }, + }, + }; +end + +local count = 0; +local temp = {}; + +local function SetOption(info, value) + local groupName = groupIdToName[info[2]]; + local optionName = info[#info]; + + -- No need to store a setting if it's disabled (false) + if not value and info.arg and not info.arg:find("override") then + value = nil; + + -- If this is an override toggler then also set the related field to nil + addon.db.global.groups[groupName][info.arg] = nil; + end + + addon.db.global.groups[groupName][optionName] = value; +end + +local function GetOptionByKey(groupName, optionName, noDefault) + if addon.db.global.groups[groupName][optionName] ~= nil then + return addon.db.global.groups[groupName][optionName]; + elseif addon.db.global.defaults[optionName] and not noDefault then + return addon.db.global.defaults[optionName]; + else + return nil; + end +end + +local function GetOption(info) + local groupName = groupIdToName[info[2]]; + local optionName = info[#info]; + + return GetOptionByKey(groupName, optionName); +end + +local function GetDisabled(info) + if not info.arg or not info.arg:find("override") then + return false; + end + + local groupName = groupIdToName[info[2]]; + local optionName = info[#info]; + + return (GetOptionByKey(groupName, info.arg, true) == nil); +end + +local function ValidateGroupName(_, value) + value = string.lower(string.trim(value or "")); + + for name, _ in pairs(addon.db.global.groups) do + if string.lower(name) == value then + return ("A group named \"%s\" already exists."):format(name); + end + end + + return true; +end + +local function InGroup(itemId) + -- Go through all groups to see if this item is already somewhere + for groupName, values in pairs(addon.db.global.groups) do + if values.items and values.items[itemId] then + return groupName; + end + end + + return; +end + +local function AddToGroup(groupName, itemId) + if InGroup(itemId) then + return false; + end + + if not addon.db.global.groups[groupName].items then + addon.db.global.groups[groupName].items = {}; + end + + -- Set this item + addon.db.global.groups[groupName].items[itemId] = true; + + -- Now rebuild the list + AceConfigRegistry:NotifyChange("InventoryOptions"); + + return true; +end + +local tblAddItemTemplate = { + order = 0, + type = "input", + name = function(info) + local itemName, _, itemRarity = GetItemInfo(info[#info]); + return tostring( 7 - (itemRarity or 0) ) .. (itemName or ""); + end, + get = function(info) + return tostring(info[#info]); -- Ace is going to be anal about this if it's a numeric value, so we transmute it into a string here then back to a number on the other side + end, + set = function(widget, info) + -- This is NOT a real "set", we pass the widget reference to this function which contains similar, but not the same, info. + + if widget.itemId then + local groupName = groupIdToName[info[2]]; + + if not AddToGroup(groupName, widget.itemId) then + print("|cffff0000Couldn't add the item with itemId (" .. widget.itemId .. ") because it is already in a group.|r"); + end + end + end, + width = "double", + dialogControl = "ItemLinkButton", +}; + +local tblRemoveItemTemplate = { + order = 0, + type = "input", + name = function(info) + local itemName, _, itemRarity = GetItemInfo(info[#info]); + return tostring( 7 - (itemRarity or 0) ) .. (itemName or ""); + end, + get = function(info) + return tostring(info[#info]); -- Ace is going to be anal about this if it's a numeric value, so we transmute it into a string here then back to a number on the other side + end, + set = function(widget, info) + -- This is NOT a real "set", we pass the widget reference to this function which contains similar, but not the same, info. + + if widget.itemId then + local groupName = groupIdToName[info[2]]; + + -- Unset this item + addon.db.global.groups[groupName].items[widget.itemId] = nil; + + -- Now rebuild the list + AceConfigRegistry:NotifyChange("InventoryOptions"); + end + end, + width = "double", + dialogControl = "ItemLinkButton", +}; + +local function UpdateAddItemList(info) + local groupName = groupIdToName[info[2]]; + + if not addon.db.global.groups[groupName].items then + addon.db.global.groups[groupName].items = {}; + end + + -- Merge all items from all groups together + local items = {}; + for groupName, values in pairs(addon.db.global.groups) do + if values.items then + for itemId, _ in pairs(values.items) do + items[itemId] = true; + end + end + end + + local ref = options.args.groups.args[info[2]].args.add.args.list.args; + + -- Parse bags and show these + for bagID = 4, 0, -1 do + for slot = 1, GetContainerNumSlots(bagID) do + local itemId = addon:GetItemId(GetContainerItemLink(bagID, slot)); + + if itemId then + if not items[itemId] then + -- If this item isn't used in any group yet + ref[itemId] = tblAddItemTemplate; + else + -- It's already used in a group, don't show it + ref[itemId] = nil; + end + end + end + end +end + +local function UpdateRemoveItemList(info) + local groupName = groupIdToName[info[2]]; + + if not addon.db.global.groups[groupName].items then + addon.db.global.groups[groupName].items = {}; + end + + local ref = options.args.groups.args[info[2]].args.remove.args.list.args; + + -- Unset all + for itemId, _ in pairs(ref) do + ref[itemId] = nil; + end + + -- Parse items in group and show these + for itemId, _ in pairs(addon.db.global.groups[groupName].items) do + ref[itemId] = tblRemoveItemTemplate; + end +end + +function addon:GetItemId(itemLink) + itemLink = itemLink and select(3, string.find(itemLink, "|Hitem:([-0-9]+):")); -- if itemLink is nil, it won't execute the second part + itemLink = itemLink and tonumber(itemLink); + + return itemLink; +end + +-- Default group +local defaultGroup = { + order = 0, + type = "group", + childGroups = "tab", + name = function(info) + return groupIdToName[info[#info]]; + end, + args = { + general = { + order = 10, + type = "group", + name = "Stock settings", + desc = "Change the stock settings for just this group.", + args = { + minimumStock = { + order = 10, + type = "group", + inline = true, + name = "Minimum stock", + set = SetOption, + get = GetOption, + disabled = GetDisabled, + args = { + description = { + order = 0, + type = "description", + name = "Here you can specify the minimum amount of items you wish to keep in stock and related settings for the currently selected group.", + }, + header = { + order = 5, + type = "header", + name = "", + }, + overrideMinimumStock = { + order = 9, + type = "toggle", + name = "Override min stock", + desc = "Allows you to override the minimum stock setting for this group.", + arg = "minimumStock", + }, + minimumStock = { + order = 10, + type = "range", + min = 0, + max = 100000, + softMax = 1000, + step = 1, + name = "Minimum stock", + desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.", + arg = "overrideMinimumStock", + }, + overrideSummaryThresholdShow = { + order = 19, + type = "toggle", + name = "Override summary showing", + desc = "Allows you to override when this group should appear in the summary.", + arg = "summaryThresholdShow", + }, + summaryThresholdShow = { + order = 20, + type = "range", + min = 0, + max = 100, + softMax = 10, + step = 0.05, + isPercent = true, + name = "Show in summary when below", + desc = "Show items in the summary when below the specified percentage of the minimum stock.\n\nYou can manually enter a value between 1.000% and 10.000% in the edit box if the provided range is insufficient.", + arg = "overrideSummaryThresholdShow", + }, + overrideAlertBelowMinimum = { + order = 29, + type = "toggle", + name = "Override minimum alert", + desc = "Allows you to override wether an alert should be shown when an item in this group gets below the minimum stock threshold.", + arg = "alertBelowMinimum", + }, + alertBelowMinimum = { + order = 30, + type = "toggle", + name = "Alert when below minimum", + desc = "Show an alert when an item in this group gets below the minimum stock threshold.", + arg = "overrideAlertBelowMinimum", + }, + }, + }, + refill = { + order = 20, + type = "group", + inline = true, + name = "Replenishing stock", + set = SetOption, + get = GetOption, + disabled = GetDisabled, + args = { + description = { + order = 0, + type = "description", + name = function(info) + local groupName = groupIdToName[info[2]]; + local r = "Here you can specify the amount of items to which you wish to restock when you are collecting new items for the currently selected group. This may be higher than the minimum stock.\n\n"; + + r = r .. "When restocking the target amount is |cfffed000" .. GetOptionByKey(groupName, "restockTarget") .. "|r of every item. Not queueing craftable items when only missing |cfffed000" .. floor( GetOptionByKey(groupName, "minCraftingQueue") * GetOptionByKey(groupName, "restockTarget") ) .. "|r (|cfffed000" .. ( GetOptionByKey(groupName, "minCraftingQueue") * 100 ) .. "%|r) of the restock target."; + + return r; + end, + }, + header = { + order = 5, + type = "header", + name = "", + }, + overrideRestockTarget = { + order = 9, + type = "toggle", + name = "Override restock target", + desc = "Allows you to override the restock target setting for this group.", + arg = "restockTarget", + }, + restockTarget = { + order = 10, + type = "range", + min = 0, + max = 100000, + softMax = 1000, + step = 1, + name = "Restock target", + desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.", + arg = "overrideRestockTarget", + }, + overrideMinCraftingQueue = { + order = 19, + type = "toggle", + name = "Override min queue", + desc = "Allows you to override the minimum craftable items queue setting for this group.", + arg = "minCraftingQueue", + }, + minCraftingQueue = { + order = 20, + type = "range", + min = 0, + max = 1, + step = 0.01, + isPercent = true, + name = "Don't queue if I only miss", + desc = "Don't add a craftable item to the queue if I only miss this much or less of the restock target.\n\nExample: if your restock target is set to 60 and this is set to 5%, an item won't be queued unless you are missing more than 3 of it.", + arg = "overrideMinCraftingQueue", + }, + }, + }, + }, + }, + group = { + order = 20, + type = "group", + name = "Group Management", + desc = "Rename, delete or export this group.", + args = { + rename = { + order = 10, + type = "group", + name = "Rename", + inline = true, + args = { + rename = { + order = 10, + type = "input", + name = "New group name", + desc = "Change the name of this group to something else. You can also use item links here as you wish.", + validate = ValidateGroupName, + set = function(info, value) + local oldGroupName = groupIdToName[info[2]]; + + addon.db.global.groups[value] = CopyTable(addon.db.global.groups[oldGroupName]); + addon.db.global.groups[oldGroupName] = nil; + + groupIdToName[info[2]] = value; + groupIdToName[value] = true; + groupIdToName[oldGroupName] = nil; + + addon:FillGroupOptions(); + end, + get = function(info) + return groupIdToName[info[2]]; + end, + width = "double", + }, + }, + }, + delete = { + order = 20, + type = "group", + name = "Delete", + inline = true, + args = { + delete = { + order = 10, + type = "execute", + name = "Delete group", + desc = "Delete the currently selected group.", + confirm = true, + confirmText = "Are you sure you wish to |cffff0000DELETE|r this group? This action is not reversable!", + func = function(info) + local groupName = groupIdToName[info[2]]; + + addon.db.global.groups[groupName] = nil; + + addon:FillGroupOptions(); + end, + }, + }, + }, + export = { + order = 30, + type = "group", + name = "Export", + inline = true, + args = { + input = { + order = 10, + type = "input", + multiline = true, + name = "Group data", + width = "full", + desc = "Export the group data for the currently selected group. Press CTRL-A to select all and CTRL-C to copy the text.", + set = false, + get = function(info) + local groupName = groupIdToName[info[2]]; + + -- We want to include the group name, so we copy the table then set another value + local temp = CopyTable(addon.db.global.groups[groupName]); + temp.name = groupName; + + if not AceSerializer then + AceSerializer = LibStub("AceSerializer-3.0"); + end + + return AceSerializer:Serialize(temp); + end, + }, + }, + }, + }, + }, + add = { + order = 30, + type = "group", + name = "Add items", + args = { + singleAdd = { + order = 10, + type = "group", + inline = true, + name = "Add items", + args = { + help = { + order = 10, + type = "description", + name = "You can add a single item to this group at a time by pasting the item-id or an item-link in the field to the left or you can also import multiple items at once by pasting exported item data in the field to the right. Scroll further down to add items based on your inventory contents.", + }, + itemLink = { + order = 20, + type = "input", + name = "Single item add (item-link or item-id)", + desc = "Shift-click an item-link or enter an item-id to add the related item to this group. You can only add one item link or item id at a time.", + validate = function(info, value) + -- If the value is empty we'll allow passing to clear the carret + if value == "" then return true; end + + local groupName = groupIdToName[info[2]]; + + local itemId = addon:GetItemId(string.trim(value or "")) or tonumber(string.trim(value or "")); + + if not itemId then + return "This is not a valid item link."; + elseif InGroup(itemId) then + return ("This item is already in the group \"%s\"."):format(InGroup(itemId)); + end + + return true; + end, + set = function(info, value) + if value and value ~= "" then + local groupName = groupIdToName[info[2]]; + + local itemId = addon:GetItemId(string.trim(value or "")) or tonumber(string.trim(value or "")); + + AddToGroup(groupName, itemId); + + print(("Added %s"):format(select(2, GetItemInfo(itemId)) or ("Unknown (#%d)"):format(itemId))); + end + end, + get = false, + }, + import = { + order = 40, + type = "input", + name = "Import item data", + desc = "Import item data from an exported item data-string. Any items already grouped will be skipped.", + set = function(info, value) + local groupName = groupIdToName[info[2]]; + + local allItemIds = { string.split(";", value or "") }; + + for _, value in pairs(allItemIds) do + local itemId = tonumber(value); + + if not itemId then + print(("\"%s\" is not a number."):format(value)); + elseif InGroup(itemId) then + print(("Skipping %s (#%d) as it is already in the group |cfffed000%s|r."):format(select(2, GetItemInfo(itemId)) or "Unknown", itemId, InGroup(itemId))); + else + AddToGroup(groupName, itemId); + end + end + end, + get = false, + }, + }, + }, + massAdd = { + order = 20, + type = "group", + inline = true, + name = "Mass add", + args = { + help = { + order = 10, + type = "description", + name = "Click the items you wish to add to this group or add multiple of these items at once by providing a name filter in the field below.", + }, + massAdd = { + order = 20, + type = "input", + name = "Add all items matching...", + desc = "Add every item in your inventory matching the name entered in this field. If you enter \"Glyph\" as a filter, any items in your inventory containing this in their name will be added to this group.", + --set = massAddItems, + get = false, + }, + }, + }, + list = { + order = 30, + type = "group", + inline = true, + name = "Item list", + hidden = UpdateAddItemList, + args = { + + }, + }, + }, + }, + remove = { + order = 40, + type = "group", + name = "Current items", + args = { + help = { + order = 10, + type = "group", + inline = true, + name = "Help", + hidden = false, + args = { + help = { + order = 10, + type = "description", + name = "Click the items you wish to remove from this group.", + }, + }, + }, + list = { + order = 20, + type = "group", + inline = true, + name = "Item list", + hidden = UpdateRemoveItemList, + args = { + + }, + }, + export = { + order = 30, + type = "group", + name = "Export", + inline = true, + args = { + input = { + order = 10, + type = "input", + name = "Item data", + width = "full", + desc = "Export the item data for the currently selected group. Press CTRL-A to select all and CTRL-C to copy the text.", + set = false, + get = function(info) + local groupName = groupIdToName[info[2]]; + + local combinedItemIds; + -- Parse items in group and show these + for itemId, _ in pairs(addon.db.global.groups[groupName].items) do + if not combinedItemIds then + combinedItemIds = tostring(itemId); + else + combinedItemIds = combinedItemIds .. (";%d"):format(itemId); + end + end + + return combinedItemIds; -- We don't serialize this because we actually DO want people to be able to manually modify it - besides, parsing it isn't going to be hard + end, + }, + }, + }, + }, + }, + }, +}; + +function addon:MakeGroupOptions() + options.args.groups = { + order = 1100, + type = "group", + name = "Groups", + desc = "Change a group.", + args = { + create = { + order = 10, + type = "group", + inline = true, + name = "Create a brand new group", + args = { + name = { + order = 10, + type = "input", + name = "Group name", + desc = "The name of the group. You can also use item links as you wish.", + validate = ValidateGroupName, + set = function(_, value) + self.db.global.groups[value] = {}; + + addon:FillGroupOptions(); + end, + get = false, + width = "double", + }, + }, + }, + import = { + order = 20, + type = "group", + inline = true, + name = "Import a group", + args = { + input = { + order = 10, + type = "input", + multiline = true, + name = "Group data", + desc = "Paste the group data as provided by a group export. If you are trying to import multiple groups at the same time, make sure to use newlines to seperate them.", + set = function(info, value) + local temp = { string.split("\n", value or "") }; + + for no, current in pairs(temp) do + if not AceSerializer then + AceSerializer = LibStub("AceSerializer-3.0"); + end + + local result, temp = AceSerializer:Deserialize(current); + local name; + + if not temp.name then + print("|cffff0000The provided data is not supported.|r"); + return; + else + name = temp.name; + temp.name = nil; + end + + local newGroupName = string.trim(string.lower(name or "")); + + for name in pairs(self.db.global.groups) do + if string.lower(name) == newGroupName then + print(("|cffff0000A group named \"%s\" already exists.|r"):format(name)); + return; + end + end + + self.db.global.groups[name] = temp; + end + end, + get = false, + width = "full", + }, + }, + }, + }, + }; +end + +function addon:FillGroupOptions() + for id, name in pairs(groupIdToName) do + if type(name) == "string" and not self.db.global.groups[name] then + options.args.groups.args[id] = nil; + groupIdToName[id] = nil; + groupIdToName[name] = nil; + end + end + + for name, values in pairs(self.db.global.groups) do + if not groupIdToName[name] then + options.args.groups.args[tostring(count)] = CopyTable(defaultGroup); + + groupIdToName[tostring(count)] = name; + groupIdToName[name] = true; + + count = ( count + 1 ); + end + end +end + +function addon:GetItemCount(itemId) + return Altoholic:GetItemCount(itemId); +end + + + + +function addon:Debug(t) + if not self.debugChannel and self.debugChannel ~= false then + -- We want to check just once, so if you add a debug channel later just do a /reload (registering an event for this is wasted resources) + self.debugChannel = false; + + for i = 1, NUM_CHAT_WINDOWS do + local name = GetChatWindowInfo(i); + + if name:upper() == "DEBUG" then + self.debugChannel = _G["ChatFrame" .. i]; + end + end + end + + if self.debugChannel then + self.debugChannel:AddMessage(t); + end +end diff -r 000000000000 -r c6ff7ba0e8f6 Inventory.toc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Inventory.toc Thu Oct 07 17:17:43 2010 +0200 @@ -0,0 +1,12 @@ +## Interface: 30300 +## X-Compatible-With: 40000 +## Title: Inventory +## Notes: Keep track of items you wish to keep enough stock on, and refill. +## Author: Zerotorescue +## Version: @project-version@ +## SavedVariables: InventoryDB + +embeds.xml + +Core.lua +Summary.lua diff -r 000000000000 -r c6ff7ba0e8f6 Libs/AceAddon-3.0/AceAddon-3.0.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceAddon-3.0/AceAddon-3.0.lua Thu Oct 07 17:17:43 2010 +0200 @@ -0,0 +1,642 @@ +--- **AceAddon-3.0** provides a template for creating addon objects. +-- It'll provide you with a set of callback functions that allow you to simplify the loading +-- process of your addon.\\ +-- Callbacks provided are:\\ +-- * **OnInitialize**, which is called directly after the addon is fully loaded. +-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. +-- * **OnDisable**, which is only called when your addon is manually being disabled. +-- @usage +-- -- A small (but complete) addon, that doesn't do anything, +-- -- but shows usage of the callbacks. +-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- +-- function MyAddon:OnInitialize() +-- -- do init tasks here, like loading the Saved Variables, +-- -- or setting up slash commands. +-- end +-- +-- function MyAddon:OnEnable() +-- -- Do more initialization here, that really enables the use of your addon. +-- -- Register Events, Hook functions, Create Frames, Get information from +-- -- the game that wasn't available in OnInitialize +-- end +-- +-- function MyAddon:OnDisable() +-- -- Unhook, Unregister Events, Hide frames that you created. +-- -- You would probably only use an OnDisable if you want to +-- -- build a "standby" mode, or be able to toggle modules on/off. +-- end +-- @class file +-- @name AceAddon-3.0.lua +-- @release $Id: AceAddon-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ + +local MAJOR, MINOR = "AceAddon-3.0", 5 +local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceAddon then return end -- No Upgrade needed. + +AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame +AceAddon.addons = AceAddon.addons or {} -- addons in general +AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon. +AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized +AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled +AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon + +-- Lua APIs +local tinsert, tconcat, tremove = table.insert, table.concat, table.remove +local fmt, tostring = string.format, tostring +local select, pairs, next, type, unpack = select, pairs, next, type, unpack +local loadstring, assert, error = loadstring, assert, error +local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler + +--[[ + xpcall safecall implementation +]] +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local xpcall, eh = ... + local method, ARGS + local function call() return method(ARGS) end + + local function dispatch(func, ...) + method = func + if not method then return end + ARGS = ... + return xpcall(call, eh) + end + + return dispatch + ]] + + local ARGS = {} + for i = 1, argCount do ARGS[i] = "arg"..i end + code = code:gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) +Dispatchers[0] = function(func) + return xpcall(func, errorhandler) +end + +local function safecall(func, ...) + -- we check to see if the func is passed is actually a function here and don't error when it isn't + -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not + -- present execution should continue without hinderance + if type(func) == "function" then + return Dispatchers[select('#', ...)](func, ...) + end +end + +-- local functions that will be implemented further down +local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype + +-- used in the addon metatable +local function addontostring( self ) return self.name end + +--- Create a new AceAddon-3.0 addon. +-- Any libraries you specified will be embeded, and the addon will be scheduled for +-- its OnInitialize and OnEnable callbacks. +-- The final addon object, with all libraries embeded, will be returned. +-- @paramsig [object ,]name[, lib, ...] +-- @param object Table to use as a base for the addon (optional) +-- @param name Name of the addon object to create +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a simple addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") +-- +-- -- Create a Addon object based on the table of a frame +-- local MyFrame = CreateFrame("Frame") +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0") +function AceAddon:NewAddon(objectorname, ...) + local object,name + local i=1 + if type(objectorname)=="table" then + object=objectorname + name=... + i=2 + else + name=objectorname + end + if type(name)~="string" then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) + end + if self.addons[name] then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) + end + + object = object or {} + object.name = name + + local addonmeta = {} + local oldmeta = getmetatable(object) + if oldmeta then + for k, v in pairs(oldmeta) do addonmeta[k] = v end + end + addonmeta.__tostring = addontostring + + setmetatable( object, addonmeta ) + self.addons[name] = object + object.modules = {} + object.defaultModuleLibraries = {} + Embed( object ) -- embed NewModule, GetModule methods + self:EmbedLibraries(object, select(i,...)) + + -- add to queue of addons to be initialized upon ADDON_LOADED + tinsert(self.initializequeue, object) + return object +end + + +--- Get the addon object by its name from the internal AceAddon registry. +-- Throws an error if the addon object cannot be found (except if silent is set). +-- @param name unique name of the addon object +-- @param silent if true, the addon is optional, silently return nil if its not found +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +function AceAddon:GetAddon(name, silent) + if not silent and not self.addons[name] then + error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2) + end + return self.addons[name] +end + +-- - Embed a list of libraries into the specified addon. +-- This function will try to embed all of the listed libraries into the addon +-- and error if a single one fails. +-- +-- **Note:** This function is for internal use by :NewAddon/:NewModule +-- @paramsig addon, [lib, ...] +-- @param addon addon object to embed the libs in +-- @param lib List of libraries to embed into the addon +function AceAddon:EmbedLibraries(addon, ...) + for i=1,select("#", ... ) do + local libname = select(i, ...) + self:EmbedLibrary(addon, libname, false, 4) + end +end + +-- - Embed a library into the addon object. +-- This function will check if the specified library is registered with LibStub +-- and if it has a :Embed function to call. It'll error if any of those conditions +-- fails. +-- +-- **Note:** This function is for internal use by :EmbedLibraries +-- @paramsig addon, libname[, silent[, offset]] +-- @param addon addon object to embed the library in +-- @param libname name of the library to embed +-- @param silent marks an embed to fail silently if the library doesn't exist (optional) +-- @param offset will push the error messages back to said offset, defaults to 2 (optional) +function AceAddon:EmbedLibrary(addon, libname, silent, offset) + local lib = LibStub:GetLibrary(libname, true) + if not lib and not silent then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2) + elseif lib and type(lib.Embed) == "function" then + lib:Embed(addon) + tinsert(self.embeds[addon], libname) + return true + elseif lib then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2) + end +end + +--- Return the specified module from an addon object. +-- Throws an error if the addon object cannot be found (except if silent is set) +-- @name //addon//:GetModule +-- @paramsig name[, silent] +-- @param name unique name of the module +-- @param silent if true, the module is optional, silently return nil if its not found (optional) +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- -- Get the Module +-- MyModule = MyAddon:GetModule("MyModule") +function GetModule(self, name, silent) + if not self.modules[name] and not silent then + error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2) + end + return self.modules[name] +end + +local function IsModuleTrue(self) return true end + +--- Create a new module for the addon. +-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\ +-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as +-- an addon object. +-- @name //addon//:NewModule +-- @paramsig name[, prototype|lib[, lib, ...]] +-- @param name unique name of the module +-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a module with some embeded libraries +-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") +-- +-- -- Create a module with a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") +function NewModule(self, name, prototype, ...) + if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end + if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end + + if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end + + -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. + -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. + local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) + + module.IsModule = IsModuleTrue + module:SetEnabledState(self.defaultModuleState) + module.moduleName = name + + if type(prototype) == "string" then + AceAddon:EmbedLibraries(module, prototype, ...) + else + AceAddon:EmbedLibraries(module, ...) + end + AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries)) + + if not prototype or type(prototype) == "string" then + prototype = self.defaultModulePrototype or nil + end + + if type(prototype) == "table" then + local mt = getmetatable(module) + mt.__index = prototype + setmetatable(module, mt) -- More of a Base class type feel. + end + + safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. + self.modules[name] = module + + return module +end + +--- Returns the real name of the addon or module, without any prefix. +-- @name //addon//:GetName +-- @paramsig +-- @usage +-- print(MyAddon:GetName()) +-- -- prints "MyAddon" +function GetName(self) + return self.moduleName or self.name +end + +--- Enables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback +-- and enabling all modules of the addon (unless explicitly disabled).\\ +-- :Enable() also sets the internal `enableState` variable to true +-- @name //addon//:Enable +-- @paramsig +-- @usage +-- -- Enable MyModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +function Enable(self) + self:SetEnabledState(true) + return AceAddon:EnableAddon(self) +end + +--- Disables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback +-- and disabling all modules of the addon.\\ +-- :Disable() also sets the internal `enableState` variable to false +-- @name //addon//:Disable +-- @paramsig +-- @usage +-- -- Disable MyAddon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:Disable() +function Disable(self) + self:SetEnabledState(false) + return AceAddon:DisableAddon(self) +end + +--- Enables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. +-- @name //addon//:EnableModule +-- @paramsig name +-- @usage +-- -- Enable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +-- +-- -- Enable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:EnableModule("MyModule") +function EnableModule(self, name) + local module = self:GetModule( name ) + return module:Enable() +end + +--- Disables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. +-- @name //addon//:DisableModule +-- @paramsig name +-- @usage +-- -- Disable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Disable() +-- +-- -- Disable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:DisableModule("MyModule") +function DisableModule(self, name) + local module = self:GetModule( name ) + return module:Disable() +end + +--- Set the default libraries to be mixed into all modules created by this object. +-- Note that you can only change the default module libraries before any module is created. +-- @name //addon//:SetDefaultModuleLibraries +-- @paramsig lib[, lib, ...] +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Configure default libraries for modules (all modules need AceEvent-3.0) +-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0") +-- -- Create a module +-- MyModule = MyAddon:NewModule("MyModule") +function SetDefaultModuleLibraries(self, ...) + if next(self.modules) then + error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleLibraries = {...} +end + +--- Set the default state in which new modules are being created. +-- Note that you can only change the default state before any module is created. +-- @name //addon//:SetDefaultModuleState +-- @paramsig state +-- @param state Default state for new modules, true for enabled, false for disabled +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Set the default state to "disabled" +-- MyAddon:SetDefaultModuleState(false) +-- -- Create a module and explicilty enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +function SetDefaultModuleState(self, state) + if next(self.modules) then + error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleState = state +end + +--- Set the default prototype to use for new modules on creation. +-- Note that you can only change the default prototype before any module is created. +-- @name //addon//:SetDefaultModulePrototype +-- @paramsig prototype +-- @param prototype Default prototype for the new modules (table) +-- @usage +-- -- Define a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- -- Set the default prototype +-- MyAddon:SetDefaultModulePrototype(prototype) +-- -- Create a module and explicitly Enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +-- -- should print "OnEnable called!" now +-- @see NewModule +function SetDefaultModulePrototype(self, prototype) + if next(self.modules) then + error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2) + end + if type(prototype) ~= "table" then + error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2) + end + self.defaultModulePrototype = prototype +end + +--- Set the state of an addon or module +-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize. +-- @name //addon//:SetEnabledState +-- @paramsig state +-- @param state the state of an addon or module (enabled=true, disabled=false) +function SetEnabledState(self, state) + self.enabledState = state +end + + +--- Return an iterator of all modules associated to the addon. +-- @name //addon//:IterateModules +-- @paramsig +-- @usage +-- -- Enable all modules +-- for name, module in MyAddon:IterateModules() do +-- module:Enable() +-- end +local function IterateModules(self) return pairs(self.modules) end + +-- Returns an iterator of all embeds in the addon +-- @name //addon//:IterateEmbeds +-- @paramsig +local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end + +--- Query the enabledState of an addon. +-- @name //addon//:IsEnabled +-- @paramsig +-- @usage +-- if MyAddon:IsEnabled() then +-- MyAddon:Disable() +-- end +local function IsEnabled(self) return self.enabledState end +local mixins = { + NewModule = NewModule, + GetModule = GetModule, + Enable = Enable, + Disable = Disable, + EnableModule = EnableModule, + DisableModule = DisableModule, + IsEnabled = IsEnabled, + SetDefaultModuleLibraries = SetDefaultModuleLibraries, + SetDefaultModuleState = SetDefaultModuleState, + SetDefaultModulePrototype = SetDefaultModulePrototype, + SetEnabledState = SetEnabledState, + IterateModules = IterateModules, + IterateEmbeds = IterateEmbeds, + GetName = GetName, +} +local function IsModule(self) return false end +local pmixins = { + defaultModuleState = true, + enabledState = true, + IsModule = IsModule, +} +-- Embed( target ) +-- target (object) - target object to embed aceaddon in +-- +-- this is a local function specifically since it's meant to be only called internally +function Embed(target) + for k, v in pairs(mixins) do + target[k] = v + end + for k, v in pairs(pmixins) do + target[k] = target[k] or v + end +end + + +-- - Initialize the addon after creation. +-- This function is only used internally during the ADDON_LOADED event +-- It will call the **OnInitialize** function on the addon object (if present), +-- and the **OnEmbedInitialize** function on all embeded libraries. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- @param addon addon object to intialize +function AceAddon:InitializeAddon(addon) + safecall(addon.OnInitialize, addon) + + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedInitialize, lib, addon) end + end + + -- we don't call InitializeAddon on modules specifically, this is handled + -- from the event handler and only done _once_ +end + +-- - Enable the addon after creation. +-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, +-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. +-- It will call the **OnEnable** function on the addon object (if present), +-- and the **OnEmbedEnable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Enable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:EnableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if self.statuses[addon.name] or not addon.enabledState then return false end + + -- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. + self.statuses[addon.name] = true + + safecall(addon.OnEnable, addon) + + -- make sure we're still enabled before continueing + if self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedEnable, lib, addon) end + end + + -- enable possible modules. + for name, module in pairs(addon.modules) do + self:EnableAddon(module) + end + end + return self.statuses[addon.name] -- return true if we're disabled +end + +-- - Disable the addon +-- Note: This function is only used internally. +-- It will call the **OnDisable** function on the addon object (if present), +-- and the **OnEmbedDisable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Disable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:DisableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if not self.statuses[addon.name] then return false end + + -- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. + self.statuses[addon.name] = false + + safecall( addon.OnDisable, addon ) + + -- make sure we're still disabling... + if not self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedDisable, lib, addon) end + end + -- disable possible modules. + for name, module in pairs(addon.modules) do + self:DisableAddon(module) + end + end + + return not self.statuses[addon.name] -- return true if we're disabled +end + +--- Get an iterator over all registered addons. +-- @usage +-- -- Print a list of all installed AceAddon's +-- for name, addon in AceAddon:IterateAddons() do +-- print("Addon: " .. name) +-- end +function AceAddon:IterateAddons() return pairs(self.addons) end + +--- Get an iterator over the internal status registry. +-- @usage +-- -- Print a list of all enabled addons +-- for name, status in AceAddon:IterateAddonStatus() do +-- if status then +-- print("EnabledAddon: " .. name) +-- end +-- end +function AceAddon:IterateAddonStatus() return pairs(self.statuses) end + +-- Following Iterators are deprecated, and their addon specific versions should be used +-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon) +function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end +function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end + +-- Event Handling +local function onEvent(this, event, arg1) + if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then + -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration + while(#AceAddon.initializequeue > 0) do + local addon = tremove(AceAddon.initializequeue, 1) + -- this might be an issue with recursion - TODO: validate + if event == "ADDON_LOADED" then addon.baseName = arg1 end + AceAddon:InitializeAddon(addon) + tinsert(AceAddon.enablequeue, addon) + end + + if IsLoggedIn() then + while(#AceAddon.enablequeue > 0) do + local addon = tremove(AceAddon.enablequeue, 1) + AceAddon:EnableAddon(addon) + end + end + end +end + +AceAddon.frame:RegisterEvent("ADDON_LOADED") +AceAddon.frame:RegisterEvent("PLAYER_LOGIN") +AceAddon.frame:SetScript("OnEvent", onEvent) + +-- upgrade embeded +for name, addon in pairs(AceAddon.addons) do + Embed(addon) +end diff -r 000000000000 -r c6ff7ba0e8f6 Libs/AceAddon-3.0/AceAddon-3.0.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceAddon-3.0/AceAddon-3.0.xml Thu Oct 07 17:17:43 2010 +0200 @@ -0,0 +1,4 @@ + +