TradeSkillMaster/Core/UI/Elements/ProfessionScrollingTable.lua

616 lines
20 KiB
Lua
Raw Normal View History

2020-11-13 14:13:12 -05:00
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- ProfessionScrollingTable UI Element Class.
-- This is used to display the crafts within the currently-selected profession in the CraftingUI. It is a subclass of
-- the @{ScrollingTable} class.
-- @classmod ProfessionScrollingTable
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 Log = TSM.Include("Util.Log")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local Event = TSM.Include("Util.Event")
local ItemInfo = TSM.Include("Service.ItemInfo")
local Tooltip = TSM.Include("UI.Tooltip")
local ProfessionScrollingTable = TSM.Include("LibTSMClass").DefineClass("ProfessionScrollingTable", TSM.UI.ScrollingTable)
local UIElements = TSM.Include("UI.UIElements")
UIElements.Register(ProfessionScrollingTable)
TSM.UI.ProfessionScrollingTable = ProfessionScrollingTable
local private = {
activeElements = {},
categoryInfoCache = {
parent = {},
numIndents = {},
name = {},
currentSkillLevel = {},
maxSkillLevel = {},
},
}
-- ============================================================================
-- Public Class Methods
-- ============================================================================
function ProfessionScrollingTable.__init(self)
self.__super:__init()
self._query = nil
self._isSpellId = {}
self._favoritesContextTable = nil
end
function ProfessionScrollingTable.Acquire(self)
self.__super:Acquire()
self:GetScrollingTableInfo()
:SetMenuInfo(private.MenuIterator, private.MenuClickHandler)
:NewColumn("name")
:SetTitle(L["Recipe Name"])
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextFunction(private.GetNameCellText)
:SetExpanderStateFunction(private.GetExpanderState)
:SetActionIconInfo(1, 14, private.GetFavoriteIcon, true)
:SetActionIconClickHandler(private.OnFavoriteIconClick)
:DisableHiding()
:Commit()
:NewColumn("qty")
:SetTitle(L["Craft"])
:SetFont("BODY_BODY3_MEDIUM")
:SetJustifyH("CENTER")
:SetTextFunction(private.GetQtyCellText)
:Commit()
if not TSM.IsWowClassic() then
self:GetScrollingTableInfo()
:NewColumn("rank")
:SetTitle(RANK)
:SetFont("BODY_BODY3_MEDIUM")
:SetJustifyH("CENTER")
:SetTextFunction(private.GetRankCellText)
:Commit()
end
self:GetScrollingTableInfo()
:NewColumn("craftingCost")
:SetTitle(L["Crafting Cost"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextFunction(private.GetCraftingCostCellText)
:Commit()
:NewColumn("itemValue")
:SetTitle(L["Item Value"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextFunction(private.GetItemValueCellIndex)
:Commit()
:NewColumn("profit")
:SetTitle(L["Profit"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextFunction(private.GetProfitCellText)
:Commit()
:NewColumn("profitPct")
:SetTitle("%")
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextFunction(private.GetProfitPctCellText)
:Commit()
:NewColumn("saleRate")
:SetTitleIcon("iconPack.14x14/SaleRate")
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextFunction(private.GetSaleRateCellText)
:Commit()
:Commit()
if not next(private.activeElements) then
Event.Register("CHAT_MSG_SKILL", private.OnChatMsgSkill)
end
private.activeElements[self] = true
end
function ProfessionScrollingTable.Release(self)
private.activeElements[self] = nil
if not next(private.activeElements) then
Event.Unregister("CHAT_MSG_SKILL", private.OnChatMsgSkill)
end
if self._query then
self._query:SetUpdateCallback()
self._query = nil
end
wipe(self._isSpellId)
self._favoritesContextTable = nil
for _, row in ipairs(self._rows) do
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
end
self.__super:Release()
end
-- ============================================================================
-- Public Class Methods
-- ============================================================================
--- Sets the @{DatabaseQuery} source for this table.
-- This query is used to populate the entries in the profession scrolling table.
-- @tparam ProfessionScrollingTable self The profession scrolling table object
-- @tparam DatabaseQuery query The query object
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
-- @treturn ProfessionScrollingTable The profession scrolling table object
function ProfessionScrollingTable.SetQuery(self, query, redraw)
if query == self._query and not redraw then
return self
end
if self._query then
self._query:SetUpdateCallback()
end
self._query = query
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
self:_ForceLastDataUpdate()
self:UpdateData(redraw)
return self
end
--- Sets the context table to use to store favorite craft information.
-- @tparam ProfessionScrollingTable self The profession scrolling table object
-- @tparam table tbl The context table
-- @treturn ProfessionScrollingTable The profession scrolling table object
function ProfessionScrollingTable.SetFavoritesContext(self, tbl)
assert(type(tbl) == "table")
self._favoritesContextTable = tbl
return self
end
--- Sets the context table.
-- @tparam ProfessionScrollingTable self The profession scrolling table object
-- @tparam table tbl The context table
-- @tparam table defaultTbl The default table (required fields: `colWidth`, `colHidden`, `collapsed`)
-- @treturn ProfessionScrollingTable The profession scrolling table object
function ProfessionScrollingTable.SetContextTable(self, tbl, defaultTbl)
assert(type(defaultTbl.collapsed) == "table")
tbl.collapsed = tbl.collapsed or CopyTable(defaultTbl.collapsed)
self.__super:SetContextTable(tbl, defaultTbl)
return self
end
function ProfessionScrollingTable.IsSpellIdVisible(self, spellId)
if not self._isSpellId[spellId] then
-- this spellId isn't included in the query
return false
end
local categoryId = TSM.Crafting.ProfessionScanner.GetCategoryIdBySpellId(spellId)
return not self:_IsCategoryHidden(categoryId) and not self._contextTable.collapsed[categoryId]
end
function ProfessionScrollingTable.Draw(self)
if self._lastDataUpdate == nil then
self:_IgnoreLastDataUpdate()
end
self.__super:Draw()
end
-- ============================================================================
-- Private Class Methods
-- ============================================================================
function ProfessionScrollingTable._ToggleCollapsed(self, categoryId)
self._contextTable.collapsed[categoryId] = not self._contextTable.collapsed[categoryId] or nil
if self._selection and not self:IsSpellIdVisible(self._selection) then
self:SetSelection(nil, true)
end
end
function ProfessionScrollingTable._GetTableRow(self, isHeader)
local row = self.__super:_GetTableRow(isHeader)
if not isHeader then
ScriptWrapper.Set(row._frame, "OnClick", private.RowOnClick, row)
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnClick, row)
local rankBtn = row:_GetButton()
rankBtn:SetAllPoints(row._texts.rank)
ScriptWrapper.SetPropagate(rankBtn, "OnClick")
ScriptWrapper.Set(rankBtn, "OnEnter", private.RankOnEnter, row)
ScriptWrapper.Set(rankBtn, "OnLeave", private.RankOnLeave, row)
row._buttons.rank = rankBtn
end
return row
end
function ProfessionScrollingTable._UpdateData(self)
local currentCategoryPath = TempTable.Acquire()
local foundSelection = false
-- populate the data
wipe(self._data)
wipe(self._isSpellId)
for _, spellId in self._query:Iterator() do
if self._favoritesContextTable[spellId] then
local categoryId = -1
if categoryId ~= currentCategoryPath[#currentCategoryPath] then
-- this is a new category
local newCategoryPath = TempTable.Acquire()
tinsert(newCategoryPath, 1, categoryId)
-- create new category headers
if currentCategoryPath[1] ~= categoryId then
if not self:_IsCategoryHidden(categoryId) then
tinsert(self._data, categoryId)
end
end
TempTable.Release(currentCategoryPath)
currentCategoryPath = newCategoryPath
end
foundSelection = foundSelection or spellId == self:GetSelection()
if not self._contextTable.collapsed[categoryId] and not self:_IsCategoryHidden(categoryId) then
tinsert(self._data, spellId)
self._isSpellId[spellId] = true
end
end
end
for _, spellId, categoryId in self._query:Iterator() do
if not self._favoritesContextTable[spellId] then
if categoryId ~= currentCategoryPath[#currentCategoryPath] then
-- this is a new category
local newCategoryPath = TempTable.Acquire()
local currentCategoryId = categoryId
while currentCategoryId do
tinsert(newCategoryPath, 1, currentCategoryId)
currentCategoryId = private.CategoryGetParentCategoryId(currentCategoryId)
end
-- create new category headers
for i = 1, #newCategoryPath do
local newCategoryId = newCategoryPath[i]
if currentCategoryPath[i] ~= newCategoryId then
if not self:_IsCategoryHidden(newCategoryId) then
tinsert(self._data, newCategoryId)
end
end
end
TempTable.Release(currentCategoryPath)
currentCategoryPath = newCategoryPath
end
foundSelection = foundSelection or spellId == self:GetSelection()
if not self._contextTable.collapsed[categoryId] and not self:_IsCategoryHidden(categoryId) then
tinsert(self._data, spellId)
self._isSpellId[spellId] = true
end
end
end
TempTable.Release(currentCategoryPath)
if not foundSelection then
-- try to select the first visible spellId
local newSelection = nil
for _, data in ipairs(self._data) do
if not newSelection and self._isSpellId[data] then
newSelection = data
end
end
self:SetSelection(newSelection, true)
end
end
function ProfessionScrollingTable._IsCategoryHidden(self, categoryId)
if private.IsFavoriteCategory(categoryId) then
return false
end
local parent = private.CategoryGetParentCategoryId(categoryId)
while parent do
if self._contextTable.collapsed[parent] then
return true
end
parent = private.CategoryGetParentCategoryId(parent)
end
return false
end
function ProfessionScrollingTable._SetRowData(self, row, data)
local rank = self._isSpellId[data] and TSM.Crafting.ProfessionScanner.GetRankBySpellId(data) or -1
if rank == -1 then
row._buttons.rank:Hide()
else
row._buttons.rank:Show()
end
self.__super:_SetRowData(row, data)
end
function ProfessionScrollingTable._ToggleSort(self, id)
-- do nothing
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.PopulateCategoryInfoCache(categoryId)
-- numIndents always gets set, so use that to know whether or not this category is already cached
if not private.categoryInfoCache.numIndents[categoryId] then
local name, numIndents, parentCategoryId, currentSkillLevel, maxSkillLevel = TSM.Crafting.ProfessionUtil.GetCategoryInfo(categoryId)
private.categoryInfoCache.name[categoryId] = name
private.categoryInfoCache.numIndents[categoryId] = numIndents
private.categoryInfoCache.parent[categoryId] = parentCategoryId
private.categoryInfoCache.currentSkillLevel[categoryId] = currentSkillLevel
private.categoryInfoCache.maxSkillLevel[categoryId] = maxSkillLevel
end
end
function private.CategoryGetParentCategoryId(categoryId)
private.PopulateCategoryInfoCache(categoryId)
return private.categoryInfoCache.parent[categoryId]
end
function private.CategoryGetNumIndents(categoryId)
private.PopulateCategoryInfoCache(categoryId)
return private.categoryInfoCache.numIndents[categoryId]
end
function private.CategoryGetName(categoryId)
private.PopulateCategoryInfoCache(categoryId)
return private.categoryInfoCache.name[categoryId]
end
function private.CategoryGetSkillLevel(categoryId)
private.PopulateCategoryInfoCache(categoryId)
return private.categoryInfoCache.currentSkillLevel[categoryId], private.categoryInfoCache.maxSkillLevel[categoryId]
end
function private.IsFavoriteCategory(categoryId)
return categoryId == -1
end
function private.QueryUpdateCallback(_, _, self)
self:_ForceLastDataUpdate()
self:UpdateData(true)
end
function private.MenuIterator(self, prevIndex)
if prevIndex == "CREATE_GROUPS" then
-- we're done
return
else
return "CREATE_GROUPS", L["Create Groups from Table"]
end
end
function private.MenuClickHandler(self, index1, index2)
if index1 == "CREATE_GROUPS" then
assert(not index2)
self:GetBaseElement():HideDialog()
local numCreated, numAdded = 0, 0
for _, spellId in self._query:Iterator() do
local itemString = TSM.Crafting.GetItemString(spellId)
if itemString then
local groupPath = private.GetCategoryGroupPath(TSM.Crafting.ProfessionScanner.GetCategoryIdBySpellId(spellId))
if not TSM.Groups.Exists(groupPath) then
TSM.Groups.Create(groupPath)
numCreated = numCreated + 1
end
if not TSM.Groups.IsItemInGroup(itemString) and not ItemInfo.IsSoulbound(itemString) then
TSM.Groups.SetItemGroup(itemString, groupPath)
numAdded = numAdded + 1
end
end
end
Log.PrintfUser(L["%d groups were created and %d items were added from the table."], numCreated, numAdded)
else
error("Unexpected index1: "..tostring(index1))
end
end
function private.GetCategoryGroupPath(categoryId)
local parts = TempTable.Acquire()
while categoryId do
tinsert(parts, 1, private.categoryInfoCache.name[categoryId])
categoryId = private.categoryInfoCache.parent[categoryId]
end
tinsert(parts, 1, TSM.Crafting.ProfessionUtil.GetCurrentProfessionName())
return TSM.Groups.Path.Join(TempTable.UnpackAndRelease(parts))
end
function private.GetNameCellText(self, data)
if self._isSpellId[data] then
local name = TSM.Crafting.ProfessionScanner.GetNameBySpellId(data)
local color = nil
if TSM.Crafting.ProfessionUtil.IsGuildProfession() then
color = Theme.GetProfessionDifficultyColor("easy")
elseif TSM.Crafting.ProfessionUtil.IsNPCProfession() then
color = Theme.GetProfessionDifficultyColor("nodifficulty")
else
local difficulty = TSM.Crafting.ProfessionScanner.GetDifficultyBySpellId(data)
color = Theme.GetProfessionDifficultyColor(difficulty)
end
return color:ColorText(name)
else
-- this is a category
local name = nil
if private.IsFavoriteCategory(data) then
name = L["Favorited Patterns"]
else
local currentSkillLevel, maxSkillLevel = private.CategoryGetSkillLevel(data)
name = private.CategoryGetName(data)
if name and currentSkillLevel and maxSkillLevel then
name = name.." ("..currentSkillLevel.."/"..maxSkillLevel..")"
end
end
if not name then
-- happens if we're switching to another profession
return "?"
end
if private.IsFavoriteCategory(data) or private.CategoryGetNumIndents(data) == 0 then
return Theme.GetColor("INDICATOR"):ColorText(name)
else
return Theme.GetColor("INDICATOR_ALT"):ColorText(name)
end
end
end
function private.GetExpanderState(self, data)
local indentLevel = 0
if self._isSpellId[data] then
indentLevel = 2
elseif not private.IsFavoriteCategory(data) then
indentLevel = private.CategoryGetNumIndents(data) * 2
end
return not self._isSpellId[data], not self._contextTable.collapsed[data], -indentLevel
end
function private.GetFavoriteIcon(self, data, iconIndex, isMouseOver)
if iconIndex == 1 then
if not self._isSpellId[data] then
return false, nil, true
else
return true, self._favoritesContextTable[data] and "iconPack.12x12/Star/Filled" or "iconPack.12x12/Star/Unfilled", true
end
else
error("Invalid index: "..tostring(iconIndex))
end
end
function private.OnFavoriteIconClick(self, data, iconIndex)
if iconIndex == 1 then
if self._isSpellId[data] and private.IsPlayerProfession() then
self._favoritesContextTable[data] = not self._favoritesContextTable[data] or nil
self:_ForceLastDataUpdate()
self:UpdateData(true)
end
else
error("Invalid index: "..tostring(iconIndex))
end
end
function private.GetQtyCellText(self, data)
if not self._isSpellId[data] then
return ""
end
local num, numAll = TSM.Crafting.ProfessionUtil.GetNumCraftable(data)
if num == numAll then
if num > 0 then
return Theme.GetFeedbackColor("GREEN"):ColorText(num)
end
return tostring(num)
else
if num > 0 then
return Theme.GetFeedbackColor("GREEN"):ColorText(num.."-"..numAll)
elseif numAll > 0 then
return Theme.GetFeedbackColor("YELLOW"):ColorText(num.."-"..numAll)
else
return num.."-"..numAll
end
end
end
function private.GetRankCellText(self, data)
local rank = self._isSpellId[data] and TSM.Crafting.ProfessionScanner.GetRankBySpellId(data) or -1
if rank == -1 then
return ""
end
local filled = TSM.UI.TexturePacks.GetTextureLink("iconPack.14x14/Star/Filled")
local unfilled = TSM.UI.TexturePacks.GetTextureLink("iconPack.14x14/Star/Unfilled")
assert(rank >= 1 and rank <= 3)
return strrep(filled, rank)..strrep(unfilled, 3 - rank)
end
function private.GetCraftingCostCellText(self, data)
if not self._isSpellId[data] then
return ""
end
local craftingCost = TSM.Crafting.Cost.GetCostsBySpellId(data)
return craftingCost and Money.ToString(craftingCost) or ""
end
function private.GetItemValueCellIndex(self, data)
if not self._isSpellId[data] then
return ""
end
local _, craftedItemValue = TSM.Crafting.Cost.GetCostsBySpellId(data)
return craftedItemValue and Money.ToString(craftedItemValue) or ""
end
function private.GetProfitCellText(self, data, currentTitleIndex)
if not self._isSpellId[data] then
return ""
end
local _, _, profit = TSM.Crafting.Cost.GetCostsBySpellId(data)
local color = profit and Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED")
return profit and Money.ToString(profit, color:GetTextColorPrefix()) or ""
end
function private.GetProfitPctCellText(self, data, currentTitleIndex)
if not self._isSpellId[data] then
return ""
end
local craftingCost, _, profit = TSM.Crafting.Cost.GetCostsBySpellId(data)
local color = profit and Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED")
return profit and color:ColorText(floor(profit * 100 / craftingCost).."%") or ""
end
function private.GetSaleRateCellText(self, data)
return self._isSpellId[data] and TSM.Crafting.Cost.GetSaleRateBySpellId(data) or ""
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.RowOnClick(row, mouseButton)
local scrollingTable = row._scrollingTable
local data = row:GetData()
if mouseButton == "LeftButton" then
if scrollingTable._isSpellId[data] then
scrollingTable:SetSelection(data)
else
scrollingTable:_ToggleCollapsed(data)
end
scrollingTable:UpdateData(true)
if scrollingTable._isSpellId[data] then
row:SetHighlightState("selectedHover")
else
row:SetHighlightState("hover")
end
end
end
function private.RankOnEnter(row)
local data = row:GetData()
local rank = row._scrollingTable._isSpellId[data] and TSM.Crafting.ProfessionScanner.GetRankBySpellId(data) or -1
if rank > 0 and not TSM.IsWowClassic() then
assert(not Tooltip.IsVisible())
GameTooltip:SetOwner(row._buttons.rank, "ANCHOR_PRESERVE")
GameTooltip:ClearAllPoints()
GameTooltip:SetPoint("LEFT", row._buttons.rank, "RIGHT")
GameTooltip:SetRecipeRankInfo(data, rank)
GameTooltip:Show()
end
row._frame:GetScript("OnEnter")(row._frame)
end
function private.RankOnLeave(row)
Tooltip.Hide()
row._frame:GetScript("OnLeave")(row._frame)
end
function private.IsPlayerProfession()
return not (TSM.Crafting.ProfessionUtil.IsNPCProfession() or TSM.Crafting.ProfessionUtil.IsLinkedProfession() or TSM.Crafting.ProfessionUtil.IsGuildProfession())
end
function private.OnChatMsgSkill(_, msg)
if not strmatch(msg, TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()) then
return
end
for self in pairs(private.activeElements) do
wipe(private.categoryInfoCache.numIndents)
self:_ForceLastDataUpdate()
self:_UpdateData(true)
end
end