Zerotorescue@0: local addon = LibStub("AceAddon-3.0"):GetAddon("Inventory"); Zerotorescue@1: local mod = addon:NewModule("Summary", "AceEvent-3.0", "AceTimer-3.0"); Zerotorescue@0: Zerotorescue@0: local AceGUI = LibStub("AceGUI-3.0"); Zerotorescue@0: Zerotorescue@0: function mod:OnEnable() Zerotorescue@0: self:RegisterWidgets(); Zerotorescue@0: Zerotorescue@1: -- Register our own slash commands Zerotorescue@1: addon:RegisterSlash(function() Zerotorescue@1: mod:BuildMain(); Zerotorescue@1: mod:Build(); Zerotorescue@1: end, "s", "sum", "summary"); Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: function mod:RegisterWidgets() Zerotorescue@0: -- Register InlineGroupWithButton-widget Zerotorescue@0: -- This widget adds a button next to the header of an inline group Zerotorescue@0: -- SetPoint doesn't seem usable within AceGUI. Zerotorescue@0: Zerotorescue@0: local widgetType = "InlineGroupWithButton"; Zerotorescue@0: local widgetVersion = 1; Zerotorescue@0: Zerotorescue@0: local function Constructor() Zerotorescue@0: local widget = AceGUI:Create("InlineGroup"); Zerotorescue@0: widget.type = widgetType; Zerotorescue@0: Zerotorescue@0: widget.MakeButton = function(self, buttonSettings) Zerotorescue@0: if type(buttonSettings) == "table" then Zerotorescue@0: local button = CreateFrame("Button", nil, self.frame, "UIPanelButtonTemplate"); Zerotorescue@0: button:SetText(buttonSettings.name); Zerotorescue@0: button:SetHeight(22); Zerotorescue@0: button:SetWidth(120); Zerotorescue@0: button:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -10, 5); Zerotorescue@0: button:SetScript("OnClick", buttonSettings.exec); Zerotorescue@0: button.tooltipTitle = buttonSettings.name; Zerotorescue@0: button.tooltip = buttonSettings.desc or ""; Zerotorescue@0: button:SetScript("OnEnter", function(self) Zerotorescue@0: GameTooltip:SetOwner(self, "ANCHOR_NONE") Zerotorescue@0: GameTooltip:SetPoint("BOTTOM", self, "TOP") Zerotorescue@0: GameTooltip:SetText(self.tooltipTitle, 1, .82, 0, 1) Zerotorescue@0: Zerotorescue@0: if type(self.tooltip) == "string" then Zerotorescue@0: GameTooltip:AddLine(self.tooltip, 1, 1, 1, 1); Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: GameTooltip:Show(); Zerotorescue@0: end); Zerotorescue@0: button:SetScript("OnLeave", function(self) Zerotorescue@0: GameTooltip:Hide(); Zerotorescue@0: end); Zerotorescue@0: else Zerotorescue@0: error("settings must be a table - usage: MakeButton(table);"); Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: return widget; Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion); Zerotorescue@0: end Zerotorescue@0: Zerotorescue@1: local itemsCache = {}; Zerotorescue@1: local CACHE_ITEMS_TOTAL, CACHE_ITEMS_CURRENT, CACHE_ITEMS_PER_UPDATE = 0, 0, 5; Zerotorescue@1: local cacheStart; Zerotorescue@1: Zerotorescue@0: function mod:BuildMain() Zerotorescue@1: LibStub("AceConfigDialog-3.0"):Close("InventoryOptions"); Zerotorescue@0: Zerotorescue@1: -- Main Window Zerotorescue@1: mod.frame = AceGUI:Create("Frame"); Zerotorescue@1: mod.frame:SetTitle("Inventory Summary"); Zerotorescue@1: mod.frame:SetLayout("Fill"); Zerotorescue@1: mod.frame:SetCallback("OnClose", function(widget) Zerotorescue@1: AceGUI:Release(widget); Zerotorescue@1: end); Zerotorescue@0: Zerotorescue@1: -- ScrollFrame child Zerotorescue@1: mod.scrollFrame = AceGUI:Create("ScrollFrame"); Zerotorescue@1: mod.scrollFrame:SetLayout("Flow"); Zerotorescue@0: Zerotorescue@1: mod.frame:AddChild(mod.scrollFrame); Zerotorescue@1: Zerotorescue@1: -- Reset items cache Zerotorescue@1: table.wipe(itemsCache); Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local sortMethod = "item"; Zerotorescue@0: local sortDirectory = "ASC"; Zerotorescue@0: local function ReSort(subject) Zerotorescue@0: if sortMethod == subject then Zerotorescue@0: sortDirectory = (sortDirectory == "ASC" and "DESC") or "ASC"; Zerotorescue@0: else Zerotorescue@0: sortDirectory = "ASC"; Zerotorescue@0: end Zerotorescue@0: sortMethod = subject; Zerotorescue@0: Zerotorescue@0: mod:Build(); Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: function mod:Build() Zerotorescue@1: local start = GetTime(); Zerotorescue@1: Zerotorescue@0: mod.scrollFrame:ReleaseChildren(); Zerotorescue@0: Zerotorescue@1: -- We are going to add hunderds of widgets to this container, but don't want it to also cause hunderds of reflows, thus pause reflowing and just do it once when everything is prepared Zerotorescue@1: -- This appears to be required for each container we wish to pause, so also do this for the contents Zerotorescue@1: mod.scrollFrame:PauseLayout(); Zerotorescue@1: Zerotorescue@1: local playerName = UnitName("player"); Zerotorescue@1: Zerotorescue@1: -- Go through all our stored groups Zerotorescue@0: for groupName, values in pairs(addon.db.global.groups) do Zerotorescue@1: local trackAt = (values.trackAtCharacters or (values.trackAtCharacters == nil and addon.db.global.defaults.trackAtCharacters)); Zerotorescue@1: Zerotorescue@1: -- Does this group have any items and do we want to track it at this char? Zerotorescue@1: if values.items and trackAt[playerName] then Zerotorescue@0: -- Get group settings Zerotorescue@1: local stockRequired = (values.minimumStock or (values.minimumStock == nil and addon.db.global.defaults.minimumStock)); Zerotorescue@1: local showWhenBelow = (values.summaryThresholdShow or (values.summaryThresholdShow == nil and addon.db.global.defaults.summaryThresholdShow)); Zerotorescue@1: local priceThreshold = (values.priceThreshold or (values.priceThreshold == nil and addon.db.global.defaults.priceThreshold)); Zerotorescue@1: local hideWhenBelowPriceThreshold = (values.hideFromSummaryWhenBelowPriceThreshold or (values.hideFromSummaryWhenBelowPriceThreshold == nil and addon.db.global.defaults.hideFromSummaryWhenBelowPriceThreshold)); Zerotorescue@0: Zerotorescue@0: -- Make group container Zerotorescue@0: local iGroup = AceGUI:Create("InlineGroupWithButton"); Zerotorescue@1: iGroup:PauseLayout(); Zerotorescue@0: iGroup:SetTitle(groupName); Zerotorescue@0: iGroup:SetFullWidth(true); Zerotorescue@0: iGroup:SetLayout("Flow"); Zerotorescue@0: iGroup:MakeButton({ Zerotorescue@0: name = "Queue", Zerotorescue@0: desc = "Queue all items in this group.", Zerotorescue@0: exec = function() Zerotorescue@0: print(groupName); Zerotorescue@0: end, Zerotorescue@0: }); Zerotorescue@0: Zerotorescue@0: -- Headers Zerotorescue@1: Zerotorescue@0: -- Itemlink Zerotorescue@0: local lblItem = AceGUI:Create("InteractiveLabel"); Zerotorescue@0: lblItem:SetText("|cfffed000Item|r"); Zerotorescue@0: lblItem:SetFontObject(GameFontHighlight); Zerotorescue@0: lblItem:SetRelativeWidth(0.7); Zerotorescue@0: lblItem:SetCallback("OnClick", function() ReSort("item"); end); Zerotorescue@0: Zerotorescue@0: iGroup:AddChild(lblItem); Zerotorescue@0: Zerotorescue@0: -- Current quantity Zerotorescue@0: local lblQuantity = AceGUI:Create("InteractiveLabel"); Zerotorescue@0: lblQuantity:SetText("|cfffed000Cur.|r"); Zerotorescue@0: lblQuantity:SetFontObject(GameFontHighlight); Zerotorescue@1: lblQuantity:SetRelativeWidth(0.099); Zerotorescue@0: lblQuantity:SetCallback("OnClick", function() ReSort("current"); end); Zerotorescue@0: Zerotorescue@0: iGroup:AddChild(lblQuantity); Zerotorescue@0: Zerotorescue@0: -- Required stock Zerotorescue@0: local lblStockRequired = AceGUI:Create("InteractiveLabel"); Zerotorescue@0: lblStockRequired:SetText("|cfffed000Req.|r"); Zerotorescue@0: lblStockRequired:SetFontObject(GameFontHighlight); Zerotorescue@1: lblStockRequired:SetRelativeWidth(0.099); Zerotorescue@0: lblStockRequired:SetCallback("OnClick", function() ReSort("percentage"); end); Zerotorescue@0: Zerotorescue@0: iGroup:AddChild(lblStockRequired); Zerotorescue@0: Zerotorescue@1: -- Lowest value Zerotorescue@1: local lblValue = AceGUI:Create("InteractiveLabel"); Zerotorescue@1: lblValue:SetText("|cfffed000Value|r"); Zerotorescue@1: lblValue:SetFontObject(GameFontHighlight); Zerotorescue@1: lblValue:SetRelativeWidth(0.099); Zerotorescue@1: lblValue:SetCallback("OnClick", function() ReSort("value"); end); Zerotorescue@1: Zerotorescue@1: iGroup:AddChild(lblValue); Zerotorescue@1: Zerotorescue@1: if not itemsCache[groupName] then Zerotorescue@1: itemsCache[groupName] = {}; Zerotorescue@0: Zerotorescue@1: -- Sort item list Zerotorescue@1: for itemId in pairs(values.items) do Zerotorescue@1: local itemName, itemLink, itemRarity = GetItemInfo(itemId); Zerotorescue@1: Zerotorescue@1: table.insert(itemsCache[groupName], { Zerotorescue@1: id = itemId, Zerotorescue@1: name = itemName, Zerotorescue@1: link = itemLink, Zerotorescue@1: value = 0,--addon:GetAuctionValue(itemLink), Zerotorescue@1: rarity = itemRarity, Zerotorescue@1: count = 0,--addon:GetItemCount(itemId), Zerotorescue@1: set = {}, Zerotorescue@1: }); Zerotorescue@1: CACHE_ITEMS_TOTAL = CACHE_ITEMS_TOTAL + 1; Zerotorescue@1: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@1: table.sort(itemsCache[groupName], function(a, b) Zerotorescue@1: if sortMethod == "item" and a.rarity == b.rarity then Zerotorescue@1: -- Do a name-compare for items of the same rarity Zerotorescue@1: -- Otherwise epics first, then junk Zerotorescue@0: if sortDirectory == "ASC" then Zerotorescue@1: return a.name:upper() < b.name:upper(); Zerotorescue@0: else Zerotorescue@1: return a.name:upper() > b.name:upper(); Zerotorescue@1: end Zerotorescue@1: elseif sortMethod == "item" then Zerotorescue@1: if sortDirectory == "ASC" then Zerotorescue@1: return a.rarity > b.rarity; -- the comparers were reversed because we want epics first Zerotorescue@1: else Zerotorescue@1: return a.rarity < b.rarity; -- the comparers were reversed because we want epics first Zerotorescue@0: end Zerotorescue@0: elseif sortMethod == "current" then Zerotorescue@0: if sortDirectory == "ASC" then Zerotorescue@0: return a.count < b.count; Zerotorescue@0: else Zerotorescue@0: return a.count > b.count; Zerotorescue@0: end Zerotorescue@0: elseif sortMethod == "percentage" then Zerotorescue@0: if sortDirectory == "ASC" then Zerotorescue@0: return ( a.count / stockRequired ) < ( b.count / stockRequired ); Zerotorescue@0: else Zerotorescue@0: return ( a.count / stockRequired ) > ( b.count / stockRequired ); Zerotorescue@0: end Zerotorescue@1: elseif sortMethod == "value" then Zerotorescue@1: if sortDirectory == "ASC" then Zerotorescue@1: return a.value < b.value; Zerotorescue@1: else Zerotorescue@1: return a.value > b.value; Zerotorescue@1: end Zerotorescue@0: end Zerotorescue@0: end); Zerotorescue@0: Zerotorescue@0: -- Show stuff Zerotorescue@1: for i, item in pairs(itemsCache[groupName]) do Zerotorescue@1: if ( item.count / stockRequired ) < showWhenBelow and not (hideWhenBelowPriceThreshold and item.value < priceThreshold) then Zerotorescue@0: local btnItemLink = AceGUI:Create("ItemLinkButton"); Zerotorescue@1: btnItemLink:SetUserData("exec", function() Zerotorescue@1: print("Win."); Zerotorescue@1: end); Zerotorescue@0: btnItemLink:SetRelativeWidth(0.7); Zerotorescue@1: btnItemLink:SetItemId(item.id); Zerotorescue@0: Zerotorescue@0: iGroup:AddChild(btnItemLink); Zerotorescue@0: Zerotorescue@0: -- Current quantity Zerotorescue@0: local lblQuantity = AceGUI:Create("Label"); Zerotorescue@0: lblQuantity:SetText(self:ColorCode(item.count, stockRequired)); Zerotorescue@1: lblQuantity:SetRelativeWidth(0.099); Zerotorescue@0: Zerotorescue@0: iGroup:AddChild(lblQuantity); Zerotorescue@0: Zerotorescue@0: -- Required stock Zerotorescue@0: local lblStockRequired = AceGUI:Create("Label"); Zerotorescue@0: lblStockRequired:SetText(stockRequired); Zerotorescue@1: lblStockRequired:SetRelativeWidth(0.099); Zerotorescue@0: Zerotorescue@0: iGroup:AddChild(lblStockRequired); Zerotorescue@1: Zerotorescue@1: -- Value Zerotorescue@1: local lblValue = AceGUI:Create("Label"); Zerotorescue@1: lblValue:SetText(self:DisplayMoney(item.value, priceThreshold)); Zerotorescue@1: lblValue:SetRelativeWidth(0.099); Zerotorescue@1: Zerotorescue@1: iGroup:AddChild(lblValue); Zerotorescue@1: Zerotorescue@1: -- Remember references to the value and current fields so we can fill them later Zerotorescue@1: if item.set then Zerotorescue@1: item.set.value = lblValue; Zerotorescue@1: item.set.current = lblQuantity; Zerotorescue@1: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@1: iGroup:ResumeLayout(); Zerotorescue@1: mod.scrollFrame:AddChild(iGroup); -- this can take up to .5 seconds, might need to look into again at a later time Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@1: Zerotorescue@1: mod.scrollFrame:ResumeLayout(); Zerotorescue@1: mod.scrollFrame:DoLayout(); Zerotorescue@1: Zerotorescue@1: if CACHE_ITEMS_TOTAL > 0 then Zerotorescue@1: cacheStart = GetTime(); Zerotorescue@1: self.tmrUpdater = self:ScheduleRepeatingTimer("UpdateNextItem", .01); -- Once every 100 frames (or once every x frames if you have less than 100 FPS, basically, once every frame) Zerotorescue@1: end Zerotorescue@1: end Zerotorescue@1: Zerotorescue@1: function mod:UpdateNextItem() Zerotorescue@1: local i = 0; Zerotorescue@1: Zerotorescue@1: for groupName, items in pairs(itemsCache) do Zerotorescue@1: local minimumStock = addon:GetOptionByKey(groupName, "minimumStock"); Zerotorescue@1: local priceThreshold = addon:GetOptionByKey(groupName, "priceThreshold"); Zerotorescue@1: Zerotorescue@1: for _, item in pairs(items) do Zerotorescue@1: if item.set then Zerotorescue@1: item.count = addon:GetItemCount(item.id); Zerotorescue@1: if item.set.current then Zerotorescue@1: item.set.current:SetText(self:ColorCode(item.count, minimumStock)); Zerotorescue@1: end Zerotorescue@1: Zerotorescue@1: item.value = addon:GetAuctionValue(item.link); Zerotorescue@1: if item.set.value then Zerotorescue@1: item.set.value:SetText(self:DisplayMoney(item.value, priceThreshold)); Zerotorescue@1: end Zerotorescue@1: Zerotorescue@1: item.set = nil; Zerotorescue@1: Zerotorescue@1: i = i + 1; Zerotorescue@1: CACHE_ITEMS_CURRENT = CACHE_ITEMS_CURRENT + 1; Zerotorescue@1: Zerotorescue@1: mod.frame:SetStatusText("Caching auction values and item-counts... " .. floor(CACHE_ITEMS_CURRENT / CACHE_ITEMS_TOTAL * 100) .. "% has already been processed."); Zerotorescue@1: Zerotorescue@1: if i >= CACHE_ITEMS_PER_UPDATE then Zerotorescue@1: return; Zerotorescue@1: end Zerotorescue@1: end Zerotorescue@1: end Zerotorescue@1: end Zerotorescue@1: Zerotorescue@1: -- Reset trackers Zerotorescue@1: CACHE_ITEMS_TOTAL = 0; Zerotorescue@1: CACHE_ITEMS_CURRENT = 0; Zerotorescue@1: Zerotorescue@1: -- Rebuild list so hidden items due to too low prices get added Zerotorescue@1: self:Build(); Zerotorescue@1: Zerotorescue@1: -- Stop timer Zerotorescue@1: self:CancelTimer(self.tmrUpdater, true); Zerotorescue@1: Zerotorescue@1: -- Announce Zerotorescue@1: mod.frame:SetStatusText("All prices and itemcounts have been cached. This process took " .. ceil(GetTime() - cacheStart) .. " seconds."); Zerotorescue@1: Zerotorescue@1: -- Forget time Zerotorescue@1: cacheStart = nil; Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: function mod:ColorCode(num, required) Zerotorescue@0: local percentage = ( num / required ); Zerotorescue@0: Zerotorescue@0: if percentage >= addon.db.global.defaults.colors.green then Zerotorescue@0: return ("|cff00ff00%d|r"):format(num); Zerotorescue@0: elseif percentage >= addon.db.global.defaults.colors.yellow then Zerotorescue@0: return ("|cffffff00%d|r"):format(num); Zerotorescue@0: elseif percentage >= addon.db.global.defaults.colors.orange then Zerotorescue@0: return ("|cffff9933%d|r"):format(num); Zerotorescue@0: elseif percentage >= addon.db.global.defaults.colors.red then Zerotorescue@0: return ("|cffff0000%d|r"):format(num); Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@1: function mod:DisplayMoney(value, priceThreshold) Zerotorescue@1: if value < priceThreshold then Zerotorescue@1: return ("|cffff0000%s|r"):format(addon:ReadableMoney(value or 0, true)); Zerotorescue@1: else Zerotorescue@1: return addon:ReadableMoney(value or 0, true); Zerotorescue@1: end Zerotorescue@1: end Zerotorescue@1: Zerotorescue@0: function mod:NumberFormat(num) Zerotorescue@0: local formatted = string.gsub(num, "(%d)(%d%d%d)$", "%1,%2", 1); Zerotorescue@0: Zerotorescue@0: while true do Zerotorescue@0: formatted, matches = string.gsub(formatted, "(%d)(%d%d%d),", "%1,%2,", 1); Zerotorescue@0: Zerotorescue@0: if matches == 0 then Zerotorescue@0: break; Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: return formatted; Zerotorescue@1: end Zerotorescue@1: Zerotorescue@1: --[[ Zerotorescue@1: No longer used, we're now caching complete item data instead of just AH values. Zerotorescue@1: local AuctionValueCache = {}; Zerotorescue@1: function mod:GetAuctionValue(link) Zerotorescue@1: local itemId = addon:GetItemId(link); Zerotorescue@1: Zerotorescue@1: if AuctionValueCache[itemId] then Zerotorescue@1: return AuctionValueCache[itemId]; Zerotorescue@1: else Zerotorescue@1: local value = addon:GetAuctionValue(link); Zerotorescue@1: Zerotorescue@1: AuctionValueCache[itemId] = value; Zerotorescue@1: Zerotorescue@1: -- Reset the cache 1 minute after last updating it Zerotorescue@1: self:CancelTimer(self.tmrResetCache, true); Zerotorescue@1: self.tmrResetCache = self:ScheduleTimer(function() Zerotorescue@1: table.wipe(AuctionValueCache); Zerotorescue@1: Zerotorescue@1: mod.frame:SetStatusText("The auction item value cache has been reset."); Zerotorescue@1: end, 60); Zerotorescue@1: mod.frame:SetStatusText(""); Zerotorescue@1: Zerotorescue@1: return value; Zerotorescue@1: end Zerotorescue@1: end Zerotorescue@1: ]]