TradeSkillMaster/Core/UI/Elements/CraftingQueueList.lua

499 lines
17 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- Crafting Queue List UI Element Class.
-- The element used to show the queue in the Crafting UI. It is a subclass of the @{ScrollingTable} class.
-- @classmod CraftingQueueList
local _, TSM = ...
local L = TSM.Include("Locale").GetTable()
local TempTable = TSM.Include("Util.TempTable")
local Money = TSM.Include("Util.Money")
local Theme = TSM.Include("Util.Theme")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local ItemInfo = TSM.Include("Service.ItemInfo")
local Inventory = TSM.Include("Service.Inventory")
local CraftingQueueList = TSM.Include("LibTSMClass").DefineClass("CraftingQueueList", TSM.UI.ScrollingTable)
local UIElements = TSM.Include("UI.UIElements")
UIElements.Register(CraftingQueueList)
TSM.UI.CraftingQueueList = CraftingQueueList
local private = {
categoryOrder = {},
sortSelf = nil,
sortProfitCache = {},
}
local CATEGORY_SEP = "\001"
-- ============================================================================
-- Public Class Methods
-- ============================================================================
function CraftingQueueList.__init(self)
self.__super:__init()
self._collapsed = {}
self._query = nil
self._numCraftableCache = {}
self._onRowMouseDownHandler = nil
end
function CraftingQueueList.Acquire(self)
self._headerHidden = true
self.__super:Acquire()
self:SetSelectionDisabled(true)
self:GetScrollingTableInfo()
:NewColumn("name")
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetIconSize(12)
:SetExpanderStateFunction(private.GetExpanderState)
:SetIconFunction(private.GetItemIcon)
:SetIconHoverEnabled(true)
:SetIconClickHandler(private.OnItemIconClick)
:SetTextFunction(private.GetItemText)
:SetTooltipFunction(private.GetItemTooltip)
:SetActionIconInfo(1, 12, private.GetDeleteIcon, true)
:SetActionIconClickHandler(private.OnDeleteIconClick)
:Commit()
:NewColumn("qty")
:SetAutoWidth()
:SetFont("TABLE_TABLE1")
:SetJustifyH("CENTER")
:SetTextFunction(private.GetQty)
:SetActionIconInfo(1, 12, private.GetEditIcon, true)
:SetActionIconClickHandler(private.OnEditIconClick)
:Commit()
:Commit()
end
function CraftingQueueList.Release(self)
self._onRowMouseDownHandler = nil
wipe(self._numCraftableCache)
wipe(self._collapsed)
if self._query then
self._query:Release()
self._query = nil
end
for _, row in ipairs(self._rows) do
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
ScriptWrapper.Clear(row._frame, "OnMouseDown")
for _, button in pairs(row._buttons) do
ScriptWrapper.Clear(button, "OnMouseDown")
end
end
self.__super:Release()
end
--- Gets the data of the first row.
-- @tparam CraftingMatList self The crafting queue list object
-- @treturn CraftingQueueList The crafting queue list object
function CraftingQueueList.GetFirstData(self)
for _, data in ipairs(self._data) do
if type(data) ~= "string" then
return data
end
end
end
--- Registers a script handler.
-- @tparam CraftingQueueList self The crafting queue list object
-- @tparam string script The script to register for (supported scripts: `OnRowClick`, `OnRowMouseDown`)
-- @tparam function handler The script handler which will be called with the crafting queue list object followed by any
-- arguments to the script
-- @treturn CraftingQueueList The crafting queue list object
function CraftingQueueList.SetScript(self, script, handler)
if script == "OnRowMouseDown" then
self._onRowMouseDownHandler = handler
else
self.__super:SetScript(script, handler)
end
return self
end
--- Sets the @{DatabaseQuery} source for this list.
-- This query is used to populate the entries in the crafting queue list.
-- @tparam CraftingQueueList self The crafting queue list object
-- @tparam DatabaseQuery query The query object
-- @treturn CraftingQueueList The crafting queue list object
function CraftingQueueList.SetQuery(self, query)
if self._query then
self._query:Release()
end
self._query = query
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
self:_UpdateData()
return self
end
-- ============================================================================
-- Private Class Methods
-- ============================================================================
function CraftingQueueList._GetTableRow(self, isHeader)
local row = self.__super:_GetTableRow(isHeader)
if not isHeader then
ScriptWrapper.Set(row._frame, "OnMouseDown", private.RowOnMouseDown, row)
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnDoubleClick, row)
for _, button in pairs(row._buttons) do
ScriptWrapper.Set(button, "OnMouseDown", private.RowOnMouseDown, row)
end
end
return row
end
function CraftingQueueList._UpdateData(self)
wipe(self._data)
if not self._query then
return
end
local categories = TempTable.Acquire()
wipe(self._numCraftableCache)
wipe(private.sortProfitCache)
for _, row in self._query:Iterator() do
local rawCategory = strjoin(CATEGORY_SEP, row:GetFields("profession", "players"))
local category = strlower(rawCategory)
if not categories[category] then
tinsert(categories, category)
end
categories[category] = rawCategory
if not self._collapsed[rawCategory] then
local spellId = row:GetField("spellId")
self._numCraftableCache[row] = TSM.Crafting.ProfessionUtil.GetNumCraftableFromDB(spellId)
private.sortProfitCache[spellId] = TSM.Crafting.Cost.GetProfitBySpellId(spellId)
tinsert(self._data, row)
end
end
sort(categories, private.CategorySortComparator)
wipe(private.categoryOrder)
for i, category in ipairs(categories) do
private.categoryOrder[category] = i
tinsert(self._data, categories[category])
end
TempTable.Release(categories)
private.sortSelf = self
sort(self._data, private.DataSortComparator)
private.sortSelf = nil
end
function CraftingQueueList._SetCollapsed(self, data, collapsed)
self._collapsed[data] = collapsed or nil
end
function CraftingQueueList._HandleRowClick(self, data, mouseButton)
if type(data) == "string" then
self:_SetCollapsed(data, not self._collapsed[data])
self:UpdateData(true)
else
local currentRow
for _, row in ipairs(self._rows) do
if row:GetData() == data then
currentRow = row
break
end
end
if currentRow._texts.qty:IsMouseOver(0, 0, 0, 12) then
private.OnEditIconClick(self, data, 1)
else
self.__super:_HandleRowClick(data, mouseButton)
end
end
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.RowOnMouseDown(row, mouseButton)
local data = row:GetData()
if type(data) == "string" then
return
end
local self = row._scrollingTable
if self._onRowMouseDownHandler then
self:_onRowMouseDownHandler(data, mouseButton)
end
end
function private.RowOnDoubleClick(row, mouseButton)
local self = row._scrollingTable
self:_HandleRowClick(row:GetData(), mouseButton)
end
function private.GetExpanderState(self, data)
if type(data) == "string" then
return true, not self._collapsed[data], 0
else
return false, false, 0
end
end
function private.GetItemIcon(self, data)
if type(data) == "string" then
return
end
local spellId = data:GetField("spellId")
local itemString = TSM.Crafting.GetItemString(spellId)
local texture, tooltip = nil, nil
if itemString then
texture = ItemInfo.GetTexture(itemString)
tooltip = itemString
else
texture = select(3, TSM.Crafting.ProfessionUtil.GetResultInfo(spellId))
if TSM.Crafting.ProfessionState.IsClassicCrafting() then
tooltip = "craft:"..(TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId) or spellId)
else
tooltip = "enchant:"..spellId
end
return
end
return texture, tooltip
end
function private.OnItemIconClick(self, data, mouseButton)
self:_HandleRowClick(data, mouseButton)
end
function private.GetItemText(self, data)
if type(data) == "string" then
local profession, players = strsplit(CATEGORY_SEP, data)
local isValid = private.PlayersContains(players, UnitName("player")) and strlower(profession) == strlower(TSM.Crafting.ProfessionUtil.GetCurrentProfessionName() or "")
local text = Theme.GetColor("INDICATOR"):ColorText(profession.." ("..players..")")
if isValid then
return text
else
return text.." "..TSM.UI.TexturePacks.GetTextureLink("iconPack.12x12/Attention")
end
else
local spellId = data:GetField("spellId")
local itemString = TSM.Crafting.GetItemString(spellId)
return itemString and TSM.UI.GetColoredItemName(itemString) or GetSpellInfo(spellId) or "?"
end
end
function private.GetItemTooltip(self, data)
if type(data) == "string" then
local profession, players = strsplit(CATEGORY_SEP, data)
if not private.PlayersContains(players, UnitName("player")) then
return L["You are not on one of the listed characters."]
elseif strlower(profession) ~= strlower(TSM.Crafting.ProfessionUtil.GetCurrentProfessionName() or "") then
return L["This profession is not open."]
end
return
end
local spellId = data:GetField("spellId")
local numQueued = data:GetField("num")
local itemString = TSM.Crafting.GetItemString(spellId)
local name = itemString and TSM.UI.GetColoredItemName(itemString) or GetSpellInfo(spellId) or "?"
local tooltipLines = TempTable.Acquire()
tinsert(tooltipLines, name.." (x"..numQueued..")")
local numResult = TSM.Crafting.GetNumResult(spellId)
local profit = TSM.Crafting.Cost.GetProfitBySpellId(spellId)
local profitStr = profit and Money.ToString(profit * numResult, Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED"):GetTextColorPrefix()) or "---"
local totalProfitStr = profit and Money.ToString(profit * numResult * numQueued, Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED"):GetTextColorPrefix()) or "---"
tinsert(tooltipLines, L["Profit (Total)"]..": "..profitStr.." ("..totalProfitStr..")")
for _, matItemString, quantity in TSM.Crafting.MatIterator(spellId) do
local numHave = Inventory.GetBagQuantity(matItemString)
if not TSM.IsWowClassic() then
numHave = numHave + Inventory.GetReagentBankQuantity(matItemString) + Inventory.GetBankQuantity(matItemString)
end
local numNeed = quantity * numQueued
local color = Theme.GetFeedbackColor(numHave >= numNeed and "GREEN" or "RED")
tinsert(tooltipLines, color:ColorText(numHave.."/"..numNeed).." - "..(ItemInfo.GetName(matItemString) or "?"))
end
if TSM.Crafting.ProfessionUtil.GetRemainingCooldown(spellId) then
tinsert(tooltipLines, Theme.GetFeedbackColor("RED"):ColorText(L["On Cooldown"]))
end
return strjoin("\n", TempTable.UnpackAndRelease(tooltipLines)), true, true
end
function private.GetDeleteIcon(self, data, iconIndex)
assert(iconIndex == 1)
if type(data) == "string" then
return false
end
return true, "iconPack.12x12/Close/Default", true
end
function private.OnDeleteIconClick(self, data, iconIndex)
assert(iconIndex == 1 and type(data) ~= "string")
TSM.Crafting.Queue.SetNum(data:GetField("spellId"), 0)
end
function private.GetEditIcon(self, data, iconIndex)
assert(iconIndex == 1)
if type(data) == "string" then
return false
end
return true, "iconPack.12x12/Edit", true
end
function private.OnEditIconClick(self, data, iconIndex)
assert(iconIndex == 1 and type(data) ~= "string")
local currentRow = nil
for _, row in ipairs(self._rows) do
if row:GetData() == data then
currentRow = row
break
end
end
local name = private.GetItemText(self, data)
local texture, tooltip = private.GetItemIcon(self, data)
local dialogFrame = UIElements.New("Frame", "qty")
:SetLayout("HORIZONTAL")
:AddAnchor("LEFT", currentRow._frame, Theme.GetColSpacing() / 2, 0)
:AddAnchor("RIGHT", currentRow._frame, -Theme.GetColSpacing(), 0)
:SetHeight(20)
:SetContext(self)
:SetBackgroundColor("PRIMARY_BG")
:SetScript("OnHide", private.DialogOnHide)
:AddChild(UIElements.New("Button", "icon")
:SetSize(12, 12)
:SetMargin(16, 4, 0, 0)
:SetBackground(texture)
:SetTooltip(tooltip)
)
:AddChild(UIElements.New("Text", "name")
:SetWidth("AUTO")
:SetFont("ITEM_BODY3")
:SetText(name)
)
:AddChild(UIElements.New("Spacer", "spacer"))
:AddChild(UIElements.New("Input", "input")
:SetWidth(75)
:SetBackgroundColor("ACTIVE_BG")
:SetJustifyH("CENTER")
:SetContext(currentRow:GetData():GetField("spellId"))
:SetSubAddEnabled(true)
:SetValidateFunc("NUMBER", "1:9999")
:SetValue(currentRow:GetData():GetField("num"))
:SetScript("OnFocusLost", private.QtyInputOnFocusLost)
)
local baseFrame = self:GetBaseElement()
baseFrame:ShowDialogFrame(dialogFrame)
dialogFrame:GetElement("input"):SetFocused(true)
end
function private.DialogOnHide(frame)
local input = frame:GetElement("input")
TSM.Crafting.Queue.SetNum(input:GetContext(), tonumber(input:GetValue()))
frame:GetContext():Draw()
end
function private.QtyInputOnFocusLost(input)
input:GetBaseElement():HideDialog()
end
function private.GetQty(self, data)
if type(data) == "string" then
return ""
end
local numQueued = data:GetFields("num")
local numCraftable = min(self._numCraftableCache[data], numQueued)
local onCooldown = TSM.Crafting.ProfessionUtil.GetRemainingCooldown(data:GetField("spellId"))
local color = Theme.GetFeedbackColor(((numCraftable == 0 or onCooldown) and "RED") or (numCraftable < numQueued and "YELLOW") or "GREEN")
return color:ColorText(format("%s / %s", numCraftable, numQueued))
end
function private.PlayersContains(players, player)
players = strlower(players)
player = strlower(player)
return players == player or strmatch(players, "^"..player..",") or strmatch(players, ","..player..",") or strmatch(players, ","..player.."$")
end
function private.CategorySortComparator(a, b)
local aProfession, aPlayers = strsplit(CATEGORY_SEP, a)
local bProfession, bPlayers = strsplit(CATEGORY_SEP, b)
if aProfession ~= bProfession then
local currentProfession = TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()
currentProfession = strlower(currentProfession or "")
if aProfession == currentProfession then
return true
elseif bProfession == currentProfession then
return false
else
return aProfession < bProfession
end
end
local playerName = UnitName("player")
local aContainsPlayer = private.PlayersContains(aPlayers, playerName)
local bContainsPlayer = private.PlayersContains(bPlayers, playerName)
if aContainsPlayer and not bContainsPlayer then
return true
elseif bContainsPlayer and not aContainsPlayer then
return false
else
return aPlayers < bPlayers
end
end
function private.DataSortComparator(a, b)
-- sort by category
local aCategory, bCategory = nil, nil
if type(a) == "string" and type(b) == "string" then
return private.categoryOrder[strlower(a)] < private.categoryOrder[strlower(b)]
elseif type(a) == "string" then
aCategory = strlower(a)
bCategory = strlower(strjoin(CATEGORY_SEP, b:GetFields("profession", "players")))
if aCategory == bCategory then
return true
end
elseif type(b) == "string" then
aCategory = strlower(strjoin(CATEGORY_SEP, a:GetFields("profession", "players")))
bCategory = strlower(b)
if aCategory == bCategory then
return false
end
else
aCategory = strlower(strjoin(CATEGORY_SEP, a:GetFields("profession", "players")))
bCategory = strlower(strjoin(CATEGORY_SEP, b:GetFields("profession", "players")))
end
if aCategory ~= bCategory then
return private.categoryOrder[aCategory] < private.categoryOrder[bCategory]
end
-- sort spells within a category
local aSpellId = a:GetField("spellId")
local bSpellId = b:GetField("spellId")
local aNumCraftable = private.sortSelf._numCraftableCache[a]
local bNumCraftable = private.sortSelf._numCraftableCache[b]
local aNumQueued = a:GetField("num")
local bNumQueued = b:GetField("num")
local aCanCraftAll = aNumCraftable >= aNumQueued
local bCanCraftAll = bNumCraftable >= bNumQueued
if aCanCraftAll and not bCanCraftAll then
return true
elseif not aCanCraftAll and bCanCraftAll then
return false
end
local aCanCraftSome = aNumCraftable > 0
local bCanCraftSome = bNumCraftable > 0
if aCanCraftSome and not bCanCraftSome then
return true
elseif not aCanCraftSome and bCanCraftSome then
return false
end
local aProfit = private.sortProfitCache[aSpellId]
local bProfit = private.sortProfitCache[bSpellId]
if aProfit and not bProfit then
return true
elseif not aProfit and bProfit then
return false
end
if aProfit ~= bProfit then
return aProfit > bProfit
end
return aSpellId < bSpellId
end
function private.QueryUpdateCallback(_, _, self)
self:UpdateData(true)
end