Merge pull request #18 from vatichild/feat/mailbox-ui
feat: mailboxframe
This commit is contained in:
Binary file not shown.
@@ -131,6 +131,7 @@ function DB:Initialize()
|
||||
money = 0,
|
||||
bags = {},
|
||||
bank = {},
|
||||
mailbox = {}, -- Add mailbox storage
|
||||
equipped = {}, -- Add equipped items storage
|
||||
character = {}, -- Add character info storage
|
||||
lastUpdate = time(),
|
||||
@@ -153,6 +154,10 @@ function DB:Initialize()
|
||||
char.character = {}
|
||||
addon:Debug("Added character field to existing character")
|
||||
end
|
||||
if not char.mailbox then
|
||||
char.mailbox = {}
|
||||
addon:Debug("Added mailbox field to existing character")
|
||||
end
|
||||
end
|
||||
|
||||
addon:Debug("Database initialized for %s", fullName)
|
||||
@@ -215,6 +220,58 @@ function DB:SaveMoney(copper)
|
||||
end
|
||||
end
|
||||
|
||||
-- Save mailbox data
|
||||
function DB:SaveMailbox(mailboxData)
|
||||
local char = self:GetCurrentCharacter()
|
||||
if char then
|
||||
char.mailbox = mailboxData
|
||||
char.lastUpdate = time()
|
||||
addon:Debug("Saved mailbox data")
|
||||
end
|
||||
end
|
||||
|
||||
-- Add a single mail entry to a character's mailbox
|
||||
function DB:AddMailToCharacter(name, realm, mailRow)
|
||||
local fullName = name .. "-" .. (realm or playerRealm)
|
||||
local char = Guda_DB.characters[fullName]
|
||||
|
||||
if char then
|
||||
if not char.mailbox then
|
||||
char.mailbox = {}
|
||||
end
|
||||
|
||||
-- Check if this exact mail already exists (simplistic check)
|
||||
local exists = false
|
||||
for _, m in ipairs(char.mailbox) do
|
||||
if m.sender == mailRow.sender and m.subject == mailRow.subject and m.money == mailRow.money then
|
||||
if (not m.item and not mailRow.item) or (m.item and mailRow.item and m.item.name == mailRow.item.name and m.item.count == mailRow.item.count) then
|
||||
exists = true
|
||||
-- Update link/itemID if missing in existing but present in new
|
||||
if mailRow.item and m.item then
|
||||
if not m.item.link and mailRow.item.link then
|
||||
m.item.link = mailRow.item.link
|
||||
addon:Debug("Updated link for existing mail item")
|
||||
end
|
||||
if not m.item.itemID and mailRow.item.itemID then
|
||||
m.item.itemID = mailRow.item.itemID
|
||||
addon:Debug("Updated itemID for existing mail item")
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not exists then
|
||||
table.insert(char.mailbox, 1, mailRow) -- Add to beginning
|
||||
char.lastUpdate = time()
|
||||
addon:Debug("Added outgoing mail to %s's mailbox", fullName)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Get all characters (optionally filter by faction and/or realm)
|
||||
function DB:GetAllCharacters(sameFactionOnly, currentRealmOnly)
|
||||
local chars = {}
|
||||
@@ -256,6 +313,65 @@ function DB:GetCharacterBank(fullName)
|
||||
return char and char.bank or {}
|
||||
end
|
||||
|
||||
-- Get character's mailbox
|
||||
function DB:GetCharacterMailbox(fullName)
|
||||
local char = Guda_DB.characters[fullName]
|
||||
return char and char.mailbox or {}
|
||||
end
|
||||
|
||||
-- Find an item ID and link by name in any character's data
|
||||
function DB:FindItemByName(name)
|
||||
if not name or name == "" or not Guda_DB or not Guda_DB.characters then return nil, nil end
|
||||
|
||||
for fullName, char in pairs(Guda_DB.characters) do
|
||||
-- Check bags
|
||||
if char.bags then
|
||||
for bagID, bagData in pairs(char.bags) do
|
||||
if type(bagData) == "table" and bagData.slots then
|
||||
for slotID, item in pairs(bagData.slots) do
|
||||
if item and item.name == name and item.link then
|
||||
local itemID = addon.Modules.Utils:ExtractItemID(item.link)
|
||||
if itemID then return itemID, item.link end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Check bank
|
||||
if char.bank then
|
||||
for bagID, bagData in pairs(char.bank) do
|
||||
if type(bagData) == "table" and bagData.slots then
|
||||
for slotID, item in pairs(bagData.slots) do
|
||||
if item and item.name == name and item.link then
|
||||
local itemID = addon.Modules.Utils:ExtractItemID(item.link)
|
||||
if itemID then return itemID, item.link end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Check equipped
|
||||
if char.equipped then
|
||||
for slot, item in pairs(char.equipped) do
|
||||
if item and item.name == name and item.link then
|
||||
local itemID = addon.Modules.Utils:ExtractItemID(item.link)
|
||||
if itemID then return itemID, item.link end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Check mailbox
|
||||
if char.mailbox then
|
||||
for _, mail in ipairs(char.mailbox) do
|
||||
if mail.item and mail.item.name == name and mail.item.link then
|
||||
local itemID = addon.Modules.Utils:ExtractItemID(mail.item.link)
|
||||
if itemID then return itemID, mail.item.link end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Get character's equipped items
|
||||
function DB:GetCharacterEquipped(fullName)
|
||||
local char = Guda_DB.characters[fullName]
|
||||
|
||||
@@ -79,3 +79,11 @@ end
|
||||
function Events:OnPlayerLogout(callback, owner)
|
||||
self:Register("PLAYER_LOGOUT", callback, owner)
|
||||
end
|
||||
|
||||
function Events:OnMailShow(callback, owner)
|
||||
self:Register("MAIL_SHOW", callback, owner)
|
||||
end
|
||||
|
||||
function Events:OnMailClosed(callback, owner)
|
||||
self:Register("MAIL_CLOSED", callback, owner)
|
||||
end
|
||||
|
||||
@@ -102,11 +102,13 @@ addon.Modules = {
|
||||
Tooltip = {},
|
||||
BagScanner = {},
|
||||
BankScanner = {},
|
||||
MailboxScanner = {},
|
||||
MoneyTracker = {},
|
||||
EquipmentScanner = {},
|
||||
SortEngine = {},
|
||||
BagFrame = {},
|
||||
BankFrame = {},
|
||||
MailboxFrame = {},
|
||||
QuestItemBar = {},
|
||||
TrackedItemBar = {},
|
||||
SettingsPopup = {},
|
||||
|
||||
@@ -18,6 +18,7 @@ function Main:Initialize()
|
||||
-- Initialize scanners
|
||||
addon.Modules.BagScanner:Initialize()
|
||||
addon.Modules.BankScanner:Initialize()
|
||||
addon.Modules.MailboxScanner:Initialize()
|
||||
addon.Modules.MoneyTracker:Initialize()
|
||||
addon.Modules.EquipmentScanner:Initialize()
|
||||
|
||||
@@ -25,6 +26,7 @@ function Main:Initialize()
|
||||
addon:Print("Initializing UI...")
|
||||
addon.Modules.BagFrame:Initialize()
|
||||
addon.Modules.BankFrame:Initialize()
|
||||
addon.Modules.MailboxFrame:Initialize()
|
||||
|
||||
addon:Debug("Checking QuestItemBar module...")
|
||||
if addon.Modules.QuestItemBar and addon.Modules.QuestItemBar.isLoaded then
|
||||
@@ -103,6 +105,10 @@ function Main:SetupSlashCommands()
|
||||
-- Toggle bank
|
||||
addon.Modules.BankFrame:Toggle()
|
||||
|
||||
elseif msg == "mail" or msg == "mailbox" then
|
||||
-- Toggle mailbox
|
||||
addon.Modules.MailboxFrame:Toggle()
|
||||
|
||||
elseif msg == "sort" then
|
||||
-- Sort bags
|
||||
addon.Modules.SortEngine:SortBags()
|
||||
@@ -143,6 +149,7 @@ function Main:SetupSlashCommands()
|
||||
addon:Print("Commands:")
|
||||
addon:Print("/guda - Toggle bags")
|
||||
addon:Print("/guda bank - Toggle bank")
|
||||
addon:Print("/guda mail - Toggle mailbox")
|
||||
addon:Print("/guda sort - Sort bags")
|
||||
addon:Print("/guda sortbank - Sort bank")
|
||||
addon:Print("/guda track - Toggle item tracking")
|
||||
|
||||
+146
-7
@@ -7,12 +7,16 @@ addon.Modules.Tooltip = Tooltip
|
||||
-- Helper function to get item ID from link (Lua 5.0 compatible)
|
||||
local function GetItemIDFromLink(link)
|
||||
if not link then return nil end
|
||||
local _, _, itemID = strfind(link, "item:(%d+):?")
|
||||
if type(link) == "number" then return link end
|
||||
|
||||
-- Try to find itemID in a standard link or a raw item:ID string
|
||||
local _, _, itemID = string.find(link, "item:(%d+)")
|
||||
return itemID and tonumber(itemID) or nil
|
||||
end
|
||||
local function CountCurrentCharacterItems(itemID)
|
||||
local bagCount = 0
|
||||
local bankCount = 0
|
||||
local mailCount = 0
|
||||
local equippedCount = 0
|
||||
|
||||
-- Count current character's bags in real-time
|
||||
@@ -83,6 +87,74 @@ local function CountCurrentCharacterItems(itemID)
|
||||
end
|
||||
end
|
||||
|
||||
-- Count current character's mailbox in real-time if mailbox is open
|
||||
if addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen() then
|
||||
local numInboxItems = GetInboxNumItems()
|
||||
for i = 1, numInboxItems do
|
||||
local _, _, _, _, _, _, _, hasItem = GetInboxHeaderInfo(i)
|
||||
if hasItem then
|
||||
-- Turtle WoW supports up to 12 attachments per mail.
|
||||
-- We use GetInboxNumAttachments if available to avoid over-scanning.
|
||||
local numAttachments = 0
|
||||
if GetInboxNumAttachments then
|
||||
numAttachments = GetInboxNumAttachments(i) or 0
|
||||
end
|
||||
|
||||
-- Fallback: if we don't have the count but header says there's an item, assume at least 1.
|
||||
if numAttachments == 0 and hasItem then
|
||||
numAttachments = 1
|
||||
end
|
||||
|
||||
for j = 1, numAttachments do -- Turtle WoW supports up to 12 attachments
|
||||
local name, _, count = GetInboxItem(i, j)
|
||||
if name then
|
||||
local itemLink = addon.Modules.Utils:GetInboxItemLink(i, j)
|
||||
if itemLink then
|
||||
local slotItemID = GetItemIDFromLink(itemLink)
|
||||
if slotItemID == itemID then
|
||||
mailCount = mailCount + (count or 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Mailbox not open - use saved data
|
||||
local playerName = addon.Modules.DB:GetPlayerFullName()
|
||||
local charData = Guda_DB and Guda_DB.characters and Guda_DB.characters[playerName]
|
||||
if charData and charData.mailbox and type(charData.mailbox) == "table" then
|
||||
for _, mail in ipairs(charData.mailbox) do
|
||||
if mail.items then
|
||||
for _, item in ipairs(mail.items) do
|
||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
||||
if slotItemID == itemID then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
elseif not slotItemID and item.name then
|
||||
-- Fallback to name matching if link is missing
|
||||
local targetName = GetItemInfo(itemID)
|
||||
if targetName == item.name then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif mail.item then -- Fallback for single item data structure
|
||||
local item = mail.item
|
||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
||||
if slotItemID == itemID then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
elseif not slotItemID and item.name then
|
||||
-- Fallback to name matching if link is missing
|
||||
local targetName = GetItemInfo(itemID)
|
||||
if targetName == item.name then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Count equipped items in real-time
|
||||
for slotID = 1, 19 do -- All equipment slots
|
||||
local link = GetInventoryItemLink("player", slotID)
|
||||
@@ -94,7 +166,7 @@ local function CountCurrentCharacterItems(itemID)
|
||||
end
|
||||
end
|
||||
|
||||
return bagCount, bankCount, equippedCount
|
||||
return bagCount, bankCount, equippedCount, mailCount
|
||||
end
|
||||
|
||||
-- Count items for a specific character with real-time data for current character
|
||||
@@ -107,6 +179,7 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar)
|
||||
-- For other characters, use saved data
|
||||
local bagCount = 0
|
||||
local bankCount = 0
|
||||
local mailCount = 0
|
||||
local equippedCount = 0
|
||||
|
||||
-- Count bags from saved data
|
||||
@@ -141,6 +214,38 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar)
|
||||
end
|
||||
end
|
||||
|
||||
-- Count mailbox from saved data
|
||||
if characterData.mailbox and type(characterData.mailbox) == "table" then
|
||||
for _, mail in ipairs(characterData.mailbox) do
|
||||
if mail.items then
|
||||
for _, item in ipairs(mail.items) do
|
||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
||||
if slotItemID == itemID then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
elseif not slotItemID and item.name then
|
||||
-- Fallback to name matching if link is missing
|
||||
local targetName = GetItemInfo(itemID)
|
||||
if targetName == item.name then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif mail.item then -- Fallback for single item data structure
|
||||
local item = mail.item
|
||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
||||
if slotItemID == itemID then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
elseif not slotItemID and item.name then
|
||||
-- Fallback to name matching if link is missing
|
||||
local targetName = GetItemInfo(itemID)
|
||||
if targetName == item.name then
|
||||
mailCount = mailCount + (item.count or 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Count equipped items from saved data
|
||||
if characterData.equipped and type(characterData.equipped) == "table" then
|
||||
for slotName, itemData in pairs(characterData.equipped) do
|
||||
@@ -153,7 +258,7 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar)
|
||||
end
|
||||
end
|
||||
|
||||
return bagCount, bankCount, equippedCount
|
||||
return bagCount, bankCount, equippedCount, mailCount
|
||||
end
|
||||
|
||||
|
||||
@@ -187,8 +292,15 @@ function Tooltip:AddInventoryInfo(tooltip, link)
|
||||
return
|
||||
end
|
||||
|
||||
-- Guard against double-adding for the same item on the same tooltip
|
||||
if tooltip.GudaInventoryAdded == itemID then
|
||||
return
|
||||
end
|
||||
tooltip.GudaInventoryAdded = itemID
|
||||
|
||||
local totalBags = 0
|
||||
local totalBank = 0
|
||||
local totalMail = 0
|
||||
local totalEquipped = 0
|
||||
local characterCounts = {}
|
||||
local hasAnyItems = false
|
||||
@@ -201,18 +313,20 @@ function Tooltip:AddInventoryInfo(tooltip, link)
|
||||
-- Ensure charData is actually a table and on current realm
|
||||
if type(charData) == "table" and charData.realm == currentRealm then
|
||||
local isCurrentChar = (charName == currentPlayerName)
|
||||
local bagCount, bankCount, equippedCount = CountItemsForCharacter(itemID, charData, isCurrentChar)
|
||||
local bagCount, bankCount, equippedCount, mailCount = CountItemsForCharacter(itemID, charData, isCurrentChar)
|
||||
|
||||
if bagCount > 0 or bankCount > 0 or equippedCount > 0 then
|
||||
if bagCount > 0 or bankCount > 0 or equippedCount > 0 or mailCount > 0 then
|
||||
hasAnyItems = true
|
||||
totalBags = totalBags + bagCount
|
||||
totalBank = totalBank + bankCount
|
||||
totalMail = totalMail + mailCount
|
||||
totalEquipped = totalEquipped + equippedCount
|
||||
table.insert(characterCounts, {
|
||||
name = charData.name or charName,
|
||||
classToken = charData.classToken,
|
||||
bagCount = bagCount,
|
||||
bankCount = bankCount,
|
||||
mailCount = mailCount,
|
||||
equippedCount = equippedCount,
|
||||
isCurrent = isCurrentChar
|
||||
})
|
||||
@@ -221,7 +335,7 @@ function Tooltip:AddInventoryInfo(tooltip, link)
|
||||
-- If charData is not a table (string, number, etc.), just skip it
|
||||
end
|
||||
|
||||
local totalCount = totalBags + totalBank + totalEquipped
|
||||
local totalCount = totalBags + totalBank + totalMail + totalEquipped
|
||||
|
||||
if hasAnyItems then
|
||||
|
||||
@@ -233,7 +347,16 @@ function Tooltip:AddInventoryInfo(tooltip, link)
|
||||
|
||||
-- Total line with cyan label and white count
|
||||
local totalText = "|cFF00FFFFTotal|r: |cFFFFFFFF" .. totalCount .. "|r"
|
||||
local breakdownText = "(|cFF00FFFFBags|r: |cFFFFFFFF" .. totalBags .. "|r | |cFF00FFFFBank|r: |cFFFFFFFF" .. totalBank .. "|r)"
|
||||
local breakdownParts = {}
|
||||
if totalBags > 0 then table.insert(breakdownParts, "|cFF00FFFFBags|r: |cFFFFFFFF" .. totalBags .. "|r") end
|
||||
if totalBank > 0 then table.insert(breakdownParts, "|cFF00FFFFBank|r: |cFFFFFFFF" .. totalBank .. "|r") end
|
||||
if totalMail > 0 then table.insert(breakdownParts, "|cFF00FFFFMail|r: |cFFFFFFFF" .. totalMail .. "|r") end
|
||||
if totalEquipped > 0 then table.insert(breakdownParts, "|cFF00FFFFEquipped|r: |cFFFFFFFF" .. totalEquipped .. "|r") end
|
||||
|
||||
local breakdownText = ""
|
||||
if table.getn(breakdownParts) > 0 then
|
||||
breakdownText = "(" .. table.concat(breakdownParts, " | ") .. ")"
|
||||
end
|
||||
tooltip:AddDoubleLine(totalText, breakdownText, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
-- Sort with current character first
|
||||
@@ -254,6 +377,9 @@ function Tooltip:AddInventoryInfo(tooltip, link)
|
||||
if charInfo.bankCount > 0 then
|
||||
table.insert(parts, "|cFF00FFFFBank|r: |cFFFFFFFF" .. charInfo.bankCount .. "|r")
|
||||
end
|
||||
if charInfo.mailCount > 0 then
|
||||
table.insert(parts, "|cFF00FFFFMail|r: |cFFFFFFFF" .. charInfo.mailCount .. "|r")
|
||||
end
|
||||
if charInfo.equippedCount > 0 then
|
||||
table.insert(parts, "|cFF00FFFFEquipped|r: |cFFFFFFFF" .. charInfo.equippedCount .. "|r")
|
||||
end
|
||||
@@ -453,6 +579,19 @@ function Tooltip:Initialize()
|
||||
end)
|
||||
end
|
||||
|
||||
-- Hook SetInboxItem for mailbox
|
||||
local oldSetInboxItem = GameTooltip.SetInboxItem
|
||||
function GameTooltip:SetInboxItem(index, itemIndex)
|
||||
return WithDeferredMoney(self, function()
|
||||
local ret = oldSetInboxItem(self, index, itemIndex)
|
||||
local link = addon.Modules.Utils:GetInboxItemLink(index, itemIndex)
|
||||
if link then
|
||||
Tooltip:AddInventoryInfo(self, link)
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
end
|
||||
|
||||
-- Hook SetTradeSkillItem for profession reagents and items
|
||||
local oldSetTradeSkillItem = GameTooltip.SetTradeSkillItem
|
||||
function GameTooltip:SetTradeSkillItem(skillIndex, reagentIndex)
|
||||
|
||||
+30
-5
@@ -95,6 +95,36 @@ function Utils:GetItemInfo(itemLink)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Create a hidden tooltip for scanning
|
||||
local scanTooltip = CreateFrame("GameTooltip", "GudaBagScanTooltip", nil, "GameTooltipTemplate")
|
||||
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
|
||||
-- Get item link from mailbox attachment (WoW 1.12.1 workaround)
|
||||
function Utils:GetInboxItemLink(index, itemIndex)
|
||||
-- Try global function first (if it exists on this server/version)
|
||||
if GetInboxItemLink then
|
||||
-- Turtle WoW might support (index, itemIndex) for multiple attachments
|
||||
local link = GetInboxItemLink(index, itemIndex or 1)
|
||||
if link then return link end
|
||||
|
||||
-- Fallback to single argument if that failed
|
||||
link = GetInboxItemLink(index)
|
||||
if link then return link end
|
||||
end
|
||||
|
||||
-- In 1.12.1, GameTooltip:GetHyperlink() does not exist.
|
||||
-- Let's try to use GetItemInfo(name) as the primary way.
|
||||
local name, texture, count, quality = GetInboxItem(index, itemIndex or 1)
|
||||
if name then
|
||||
local itemName, link = GetItemInfo(name)
|
||||
if link then
|
||||
return link
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Get quality color
|
||||
function Utils:GetQualityColor(quality)
|
||||
local color = addon.Constants.QUALITY_COLORS[quality] or addon.Constants.QUALITY_COLORS[1]
|
||||
@@ -156,12 +186,7 @@ function Utils:FormatTimeAgo(timestamp)
|
||||
end
|
||||
|
||||
-- Create hidden tooltip for scanning (only once)
|
||||
local scanTooltip = nil
|
||||
local function GetScanTooltip()
|
||||
if not scanTooltip then
|
||||
scanTooltip = CreateFrame("GameTooltip", "GudaBagScanTooltip", nil, "GameTooltipTemplate")
|
||||
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
end
|
||||
return scanTooltip
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
-- Guda Mailbox Scanner
|
||||
-- Scans and stores mailbox contents
|
||||
|
||||
local addon = Guda
|
||||
|
||||
local MailboxScanner = {}
|
||||
addon.Modules.MailboxScanner = MailboxScanner
|
||||
|
||||
local mailboxOpen = false
|
||||
|
||||
-- Scan all mailbox items and return data
|
||||
function MailboxScanner:ScanMailbox()
|
||||
if not mailboxOpen then
|
||||
addon:Debug("Cannot scan mailbox - not open")
|
||||
return {}
|
||||
end
|
||||
|
||||
local mailboxData = {}
|
||||
local numItems = GetInboxNumItems()
|
||||
|
||||
for i = 1, numItems do
|
||||
local mailRows = self:ScanMailItemRows(i)
|
||||
for _, row in ipairs(mailRows) do
|
||||
table.insert(mailboxData, row)
|
||||
end
|
||||
end
|
||||
|
||||
return mailboxData
|
||||
end
|
||||
|
||||
-- Scan a single mail into one or more rows (flattened)
|
||||
function MailboxScanner:ScanMailItemRows(index)
|
||||
-- GetInboxHeaderInfo(index) returns: packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM
|
||||
local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM = GetInboxHeaderInfo(index)
|
||||
|
||||
local rows = {}
|
||||
|
||||
if hasItem then
|
||||
-- Turtle WoW supports up to 12 attachments per mail.
|
||||
-- We use GetInboxNumAttachments if available to avoid over-scanning on servers
|
||||
-- that might ignore the second argument of GetInboxItem.
|
||||
local numAttachments = 0
|
||||
if GetInboxNumAttachments then
|
||||
numAttachments = GetInboxNumAttachments(index) or 0
|
||||
end
|
||||
|
||||
-- Fallback: if we don't have the count but header says there's an item, assume at least 1.
|
||||
if numAttachments == 0 and hasItem then
|
||||
numAttachments = 1
|
||||
end
|
||||
|
||||
for itemIndex = 1, numAttachments do
|
||||
-- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse
|
||||
local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex)
|
||||
if not name then break end
|
||||
|
||||
local itemLink = addon.Modules.Utils:GetInboxItemLink(index, itemIndex)
|
||||
local itemID = itemLink and addon.Modules.Utils:ExtractItemID(itemLink)
|
||||
|
||||
-- Fallback 1: If link/itemID is missing, try GetItemInfo(name) which might be cached now
|
||||
if not itemID or not itemLink then
|
||||
local _, link = GetItemInfo(name)
|
||||
if link then
|
||||
itemLink = link
|
||||
itemID = addon.Modules.Utils:ExtractItemID(link)
|
||||
addon:Debug("Recovered link from GetItemInfo for %s", name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback 2: If still missing, try to recover from existing database (any character)
|
||||
if not itemID or not itemLink then
|
||||
itemID, itemLink = addon.Modules.DB:FindItemByName(name)
|
||||
if itemID then
|
||||
addon:Debug("Recovered link from DB for %s", name)
|
||||
end
|
||||
end
|
||||
|
||||
local itemData = {
|
||||
link = itemLink,
|
||||
texture = texture or "Interface\\Icons\\INV_Misc_Bag_08",
|
||||
count = count or 1,
|
||||
quality = quality or 0,
|
||||
name = name,
|
||||
itemID = itemID,
|
||||
}
|
||||
|
||||
-- Ensure we have a link if we have an itemID
|
||||
if itemData.itemID and not itemData.link then
|
||||
itemData.link = "item:" .. itemData.itemID .. ":0:0:0"
|
||||
end
|
||||
|
||||
-- If we have a link, try to get more detailed info from cache
|
||||
if itemData.link then
|
||||
local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemData.link)
|
||||
if itemName then
|
||||
itemData.name = itemName
|
||||
itemData.quality = itemQuality or itemData.quality
|
||||
itemData.iLevel = iLevel
|
||||
itemData.type = itemType
|
||||
itemData.class = itemCategory
|
||||
itemData.subclass = itemSubType
|
||||
itemData.equipSlot = itemEquipLoc
|
||||
if itemTexture then itemData.texture = itemTexture end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(rows, {
|
||||
sender = sender,
|
||||
subject = subject,
|
||||
money = (itemIndex == 1) and money or 0, -- Attach money only to the first row of this mail
|
||||
CODAmount = (itemIndex == 1) and CODAmount or 0,
|
||||
daysLeft = daysLeft,
|
||||
hasItem = true,
|
||||
item = itemData,
|
||||
mailIndex = index,
|
||||
itemIndex = itemIndex,
|
||||
wasRead = wasRead,
|
||||
packageIcon = packageIcon,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- If no items found but there is money or it's just a letter
|
||||
if table.getn(rows) == 0 then
|
||||
table.insert(rows, {
|
||||
sender = sender,
|
||||
subject = subject,
|
||||
money = money,
|
||||
CODAmount = CODAmount,
|
||||
daysLeft = daysLeft,
|
||||
hasItem = false,
|
||||
item = nil,
|
||||
mailIndex = index,
|
||||
itemIndex = 1,
|
||||
wasRead = wasRead,
|
||||
packageIcon = packageIcon,
|
||||
})
|
||||
end
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
-- Save current mailbox to database
|
||||
function MailboxScanner:SaveToDatabase()
|
||||
if not mailboxOpen then
|
||||
return
|
||||
end
|
||||
|
||||
local mailboxData = self:ScanMailbox()
|
||||
addon.Modules.DB:SaveMailbox(mailboxData)
|
||||
addon:Debug("Mailbox data saved")
|
||||
end
|
||||
|
||||
-- Handle outgoing mail
|
||||
function MailboxScanner:OnSendMail(recipient, subject, body)
|
||||
if not recipient or recipient == "" then return end
|
||||
|
||||
-- In WoW 1.12.1, SendMail(recipient, subject, body) is the signature.
|
||||
-- To get the attached item, we use GetSendMailItem().
|
||||
-- GetSendMailItem() returns: name, texture, count, quality
|
||||
local name, texture, count, quality = GetSendMailItem()
|
||||
local moneyAmount = GetSendMailMoney()
|
||||
|
||||
local itemData = nil
|
||||
if name then
|
||||
local _, link = GetItemInfo(name)
|
||||
itemData = {
|
||||
name = name,
|
||||
texture = texture or "Interface\\Icons\\INV_Misc_Bag_08",
|
||||
count = count or 1,
|
||||
quality = quality or 0,
|
||||
link = link,
|
||||
itemID = addon.Modules.Utils:ExtractItemID(link),
|
||||
}
|
||||
|
||||
-- Try to get more info if it's in cache
|
||||
local itemName, retLink, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(name)
|
||||
if itemName then
|
||||
itemData.link = retLink or itemData.link
|
||||
itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) or itemData.itemID
|
||||
itemData.quality = itemQuality or itemData.quality
|
||||
itemData.iLevel = iLevel
|
||||
itemData.type = itemType
|
||||
itemData.class = itemCategory
|
||||
itemData.subclass = itemSubType
|
||||
itemData.equipSlot = itemEquipLoc
|
||||
if itemTexture then itemData.texture = itemTexture end
|
||||
end
|
||||
|
||||
-- Double check itemID
|
||||
if not itemData.itemID and itemData.link then
|
||||
itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link)
|
||||
end
|
||||
end
|
||||
|
||||
if itemData or moneyAmount > 0 then
|
||||
local mailRow = {
|
||||
sender = UnitName("player"),
|
||||
subject = (subject and subject ~= "") and subject or "No Subject",
|
||||
money = moneyAmount,
|
||||
CODAmount = 0,
|
||||
daysLeft = 30, -- Outgoing mail typically has 30 days
|
||||
hasItem = itemData ~= nil,
|
||||
item = itemData,
|
||||
wasRead = false,
|
||||
}
|
||||
|
||||
addon.Modules.DB:AddMailToCharacter(recipient, nil, mailRow)
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle auction house buyouts
|
||||
function MailboxScanner:OnAuctionBid(type, index, bid)
|
||||
local name, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo(type, index)
|
||||
|
||||
if name and buyoutPrice > 0 and bid >= buyoutPrice then
|
||||
local link = GetAuctionItemLink(type, index)
|
||||
local itemData = {
|
||||
name = name,
|
||||
texture = texture or "Interface\\Icons\\INV_Misc_Bag_08",
|
||||
count = count or 1,
|
||||
quality = quality or 0,
|
||||
link = link,
|
||||
itemID = addon.Modules.Utils:ExtractItemID(link),
|
||||
}
|
||||
|
||||
-- Try to get more info if it's in cache
|
||||
local itemName, retLink, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(link or name)
|
||||
if itemName then
|
||||
itemData.link = retLink or itemData.link
|
||||
itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) or itemData.itemID
|
||||
itemData.quality = itemQuality or itemData.quality
|
||||
itemData.iLevel = iLevel
|
||||
itemData.type = itemType
|
||||
itemData.class = itemCategory
|
||||
itemData.subclass = itemSubType
|
||||
itemData.equipSlot = itemEquipLoc
|
||||
if itemTexture then itemData.texture = itemTexture end
|
||||
end
|
||||
|
||||
local mailRow = {
|
||||
sender = "Auction House",
|
||||
subject = "Auction won: " .. name,
|
||||
money = 0,
|
||||
CODAmount = 0,
|
||||
daysLeft = 30,
|
||||
hasItem = true,
|
||||
item = itemData,
|
||||
wasRead = false,
|
||||
}
|
||||
|
||||
addon.Modules.DB:AddMailToCharacter(UnitName("player"), nil, mailRow)
|
||||
addon:Debug("Captured AH buyout: %s", name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize mailbox scanner
|
||||
function MailboxScanner:Initialize()
|
||||
-- Hook SendMail to capture outgoing mail to alts
|
||||
local originalSendMail = SendMail
|
||||
SendMail = function(recipient, subject, body)
|
||||
MailboxScanner:OnSendMail(recipient, subject, body)
|
||||
return originalSendMail(recipient, subject, body)
|
||||
end
|
||||
|
||||
-- Hook PlaceAuctionBid to capture AH buyouts
|
||||
local originalPlaceAuctionBid = PlaceAuctionBid
|
||||
PlaceAuctionBid = function(type, index, bid)
|
||||
MailboxScanner:OnAuctionBid(type, index, bid)
|
||||
return originalPlaceAuctionBid(type, index, bid)
|
||||
end
|
||||
|
||||
-- Mailbox opened
|
||||
addon.Modules.Events:OnMailShow(function()
|
||||
mailboxOpen = true
|
||||
addon:Debug("Mailbox opened")
|
||||
|
||||
-- Delay scan slightly to ensure item info is available
|
||||
local frame = CreateFrame("Frame")
|
||||
local elapsed = 0
|
||||
frame:SetScript("OnUpdate", function()
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed >= 0.5 then
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
if mailboxOpen then
|
||||
MailboxScanner:SaveToDatabase()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end, "MailboxScanner")
|
||||
|
||||
-- Register for GET_ITEM_INFO_RECEIVED to refresh if item data arrives
|
||||
addon.Modules.Events:Register("GET_ITEM_INFO_RECEIVED", function()
|
||||
if mailboxOpen then
|
||||
addon:Debug("GET_ITEM_INFO_RECEIVED: Refreshing mailbox")
|
||||
MailboxScanner:SaveToDatabase()
|
||||
end
|
||||
end, "MailboxScanner")
|
||||
|
||||
-- Register for MAIL_INBOX_UPDATE to detect when mail content changes
|
||||
addon.Modules.Events:Register("MAIL_INBOX_UPDATE", function()
|
||||
if mailboxOpen then
|
||||
addon:Debug("MAIL_INBOX_UPDATE: Refreshing mailbox")
|
||||
MailboxScanner:SaveToDatabase()
|
||||
end
|
||||
end, "MailboxScanner")
|
||||
|
||||
-- Register for UI_ERROR_MESSAGE to handle "item not found" situations if needed
|
||||
-- (Some items might not be in cache and fail silently otherwise)
|
||||
|
||||
-- Mailbox closed
|
||||
addon.Modules.Events:OnMailClosed(function()
|
||||
-- Final save on close
|
||||
MailboxScanner:SaveToDatabase()
|
||||
mailboxOpen = false
|
||||
addon:Debug("Mailbox closed")
|
||||
end, "MailboxScanner")
|
||||
end
|
||||
|
||||
-- Check if mailbox is currently open
|
||||
function MailboxScanner:IsMailboxOpen()
|
||||
return mailboxOpen
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
## Title: Guda
|
||||
## Notes: All-in-one bag and bank addon for Turtle WoW
|
||||
## Author: Vati
|
||||
## Version: 1.4.8
|
||||
## Version: 1.5.0
|
||||
## SavedVariables: Guda_DB
|
||||
## SavedVariablesPerCharacter: Guda_CharDB
|
||||
|
||||
@@ -16,6 +16,7 @@ Core\Tooltip.lua
|
||||
|
||||
Data\BagScanner.lua
|
||||
Data\BankScanner.lua
|
||||
Data\MailboxScanner.lua
|
||||
Data\MoneyTracker.lua
|
||||
Data\EquipmentScanner.lua
|
||||
|
||||
@@ -23,14 +24,17 @@ Sorting\SortEngine.lua
|
||||
|
||||
# UI Lua must load before XML so OnLoad handlers exist
|
||||
UI\ItemButton.lua
|
||||
UI\FrameHelpers.lua
|
||||
UI\BagFrame.lua
|
||||
UI\BankFrame.lua
|
||||
UI\MailboxFrame.lua
|
||||
UI\QuestItemBar.lua
|
||||
UI\TrackedItemBar.lua
|
||||
UI\SettingsPopup.lua
|
||||
UI\ItemButton.xml
|
||||
UI\BagFrame.xml
|
||||
UI\BankFrame.xml
|
||||
UI\MailboxFrame.xml
|
||||
UI\SettingsPopup.xml
|
||||
|
||||
Core\Main.lua
|
||||
|
||||
+201
-138
@@ -57,6 +57,7 @@ local CATEGORY_ORDER = {
|
||||
["Weapon"] = 4, -- Non-equippable weapons
|
||||
["Armor"] = 5, -- Non-equippable armor
|
||||
["Tools"] = 6,
|
||||
["Quest"] = 7,
|
||||
["Quiver"] = 8,
|
||||
["Reagent"] = 9,
|
||||
["Trade Goods"] = 10,
|
||||
@@ -64,7 +65,6 @@ local CATEGORY_ORDER = {
|
||||
["Container"] = 12,
|
||||
["Key"] = 14,
|
||||
["Miscellaneous"] = 15,
|
||||
["Quest"] = 16,
|
||||
["Junk"] = 17,
|
||||
["Class Items"] = 18,
|
||||
}
|
||||
@@ -97,100 +97,137 @@ local GEM_PATTERNS = {
|
||||
local scanTooltip = CreateFrame("GameTooltip", "Guda_SortScanTooltip", nil, "GameTooltipTemplate")
|
||||
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
|
||||
-- Check if an item is a quest item by scanning its tooltip
|
||||
local function IsQuestItemTooltip(bagID, slotID)
|
||||
if not bagID or not slotID then return false end
|
||||
-- Property cache to prevent race conditions during rapid moves
|
||||
local propertyCache = {}
|
||||
|
||||
scanTooltip:ClearLines()
|
||||
scanTooltip:SetBagItem(bagID, slotID)
|
||||
function SortEngine:ClearCache()
|
||||
propertyCache = {}
|
||||
end
|
||||
|
||||
-- Check all tooltip lines for quest-related text (case-insensitive)
|
||||
for i = 1, scanTooltip:NumLines() do
|
||||
local line = getglobal("Guda_SortScanTooltipTextLeft" .. i)
|
||||
if line then
|
||||
local text = line:GetText()
|
||||
if text then
|
||||
local tl = string.lower(text)
|
||||
if string.find(tl, "quest starter") or
|
||||
string.find(tl, "this item begins a quest") or
|
||||
string.find(tl, "starts a quest") or
|
||||
string.find(tl, "quest item") or
|
||||
string.find(tl, "manual") then
|
||||
return true
|
||||
local function GetItemProperties(bagID, slotID, itemLink)
|
||||
if not itemLink then return nil end
|
||||
|
||||
-- Extract base item link for stable caching (item:ID:Enchant:...)
|
||||
local _, _, baseLink = string.find(itemLink, "(item:%d+:%d+:%d+:%d+:%d+:%d+:%d+:%d+)")
|
||||
local cacheKey = baseLink or itemLink
|
||||
|
||||
if propertyCache[cacheKey] then
|
||||
return propertyCache[cacheKey]
|
||||
end
|
||||
|
||||
local props = {
|
||||
isQuest = false,
|
||||
isQuestStarter = false,
|
||||
isQuestUsable = false,
|
||||
isGray = false,
|
||||
restoreTag = nil
|
||||
}
|
||||
|
||||
-- Quality check from link color code (|cff9d9d9d is gray)
|
||||
if string.find(itemLink, "|cff9d9d9d") then
|
||||
props.isGray = true
|
||||
end
|
||||
|
||||
if bagID and slotID then
|
||||
scanTooltip:ClearLines()
|
||||
scanTooltip:SetBagItem(bagID, slotID)
|
||||
|
||||
local numLines = scanTooltip:NumLines()
|
||||
if numLines and numLines > 0 then
|
||||
for i = 1, numLines do
|
||||
local line = getglobal("Guda_SortScanTooltipTextLeft" .. i)
|
||||
if line then
|
||||
local text = line:GetText()
|
||||
if text then
|
||||
local tl = string.lower(text)
|
||||
|
||||
-- Quest item check
|
||||
if string.find(tl, "quest starter") or
|
||||
string.find(tl, "this item begins a quest") or
|
||||
string.find(tl, "starts a quest") or
|
||||
string.find(tl, "quest item") or
|
||||
string.find(tl, "manual") then
|
||||
props.isQuest = true
|
||||
end
|
||||
|
||||
-- Starter check
|
||||
if string.find(tl, "quest starter") or
|
||||
string.find(tl, "this item begins a quest") or
|
||||
string.find(tl, "starts a quest") then
|
||||
props.isQuestStarter = true
|
||||
end
|
||||
|
||||
-- Usable check
|
||||
if string.find(tl, "use:") or
|
||||
string.find(tl, "right%-click") or
|
||||
string.find(tl, "right click") or
|
||||
string.find(tl, "click to") then
|
||||
props.isQuestUsable = true
|
||||
end
|
||||
|
||||
-- Restore tag check (higher priority tags override lower ones)
|
||||
if string.find(tl, "while eating") then
|
||||
props.restoreTag = "eat"
|
||||
elseif string.find(tl, "while drinking") then
|
||||
if props.restoreTag ~= "eat" then
|
||||
props.restoreTag = "drink"
|
||||
end
|
||||
elseif string.find(tl, "use: restores") then
|
||||
if not props.restoreTag then
|
||||
props.restoreTag = "restore"
|
||||
end
|
||||
end
|
||||
|
||||
-- Gray check (header line color fallback)
|
||||
if i == 1 and string.find(text, "|cff9d9d9d") then
|
||||
props.isGray = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
|
||||
propertyCache[cacheKey] = props
|
||||
return props
|
||||
end
|
||||
|
||||
-- Check if an item is a quest item by scanning its tooltip
|
||||
local function IsQuestItemTooltip(bagID, slotID)
|
||||
if not bagID or not slotID then return false end
|
||||
local link = GetContainerItemLink(bagID, slotID)
|
||||
local props = GetItemProperties(bagID, slotID, link)
|
||||
return props and props.isQuest or false
|
||||
end
|
||||
|
||||
-- Check if a quest item is usable (has 'Use:' or click to text)
|
||||
local function IsQuestItemUsable(bagID, slotID)
|
||||
if not bagID or not slotID then return false end
|
||||
|
||||
scanTooltip:ClearLines()
|
||||
scanTooltip:SetBagItem(bagID, slotID)
|
||||
|
||||
for i = 1, scanTooltip:NumLines() do
|
||||
local line = getglobal("Guda_SortScanTooltipTextLeft" .. i)
|
||||
if line then
|
||||
local text = line:GetText()
|
||||
if text then
|
||||
local tl = string.lower(text)
|
||||
if string.find(tl, "use:") or string.find(tl, "right%-click") or string.find(tl, "right click") or string.find(tl, "click to") or string.find(tl, "starts a quest") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
local link = GetContainerItemLink(bagID, slotID)
|
||||
local props = GetItemProperties(bagID, slotID, link)
|
||||
return props and props.isQuestUsable or false
|
||||
end
|
||||
|
||||
-- Check if item is a quest starter (explicit 'Starts a Quest' or 'This Item Begins a Quest')
|
||||
local function IsQuestItemStarter(bagID, slotID)
|
||||
if not bagID or not slotID then return false end
|
||||
|
||||
scanTooltip:ClearLines()
|
||||
scanTooltip:SetBagItem(bagID, slotID)
|
||||
|
||||
for i = 1, scanTooltip:NumLines() do
|
||||
local line = getglobal("Guda_SortScanTooltipTextLeft" .. i)
|
||||
if line then
|
||||
local text = line:GetText()
|
||||
if text then
|
||||
local tl = string.lower(text)
|
||||
if string.find(tl, "quest starter") or string.find(tl, "this item begins a quest") or string.find(tl, "starts a quest") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
local link = GetContainerItemLink(bagID, slotID)
|
||||
local props = GetItemProperties(bagID, slotID, link)
|
||||
return props and props.isQuestStarter or false
|
||||
end
|
||||
|
||||
-- Check if an item has a gray title in its tooltip or link
|
||||
local function IsItemGrayTooltip(bagID, slotID, itemLink)
|
||||
-- Check link first if provided
|
||||
if itemLink and string.find(itemLink, "|cff9d9d9d") then
|
||||
return true
|
||||
end
|
||||
local props = GetItemProperties(bagID, slotID, itemLink)
|
||||
return props and props.isGray or false
|
||||
end
|
||||
|
||||
if not bagID or not slotID then return false end
|
||||
|
||||
scanTooltip:ClearLines()
|
||||
scanTooltip:SetBagItem(bagID, slotID)
|
||||
|
||||
local line = getglobal("Guda_SortScanTooltipTextLeft1")
|
||||
if line then
|
||||
local text = line:GetText()
|
||||
if text then
|
||||
-- Poor quality color code is |cff9d9d9d
|
||||
if string.find(text, "|cff9d9d9d") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
-- Check if an item has a restore tag (eat, drink, or restore)
|
||||
local function GetItemRestoreTagTooltip(bagID, slotID)
|
||||
if not bagID or not slotID then return nil end
|
||||
local link = GetContainerItemLink(bagID, slotID)
|
||||
local props = GetItemProperties(bagID, slotID, link)
|
||||
return props and props.restoreTag or nil
|
||||
end
|
||||
|
||||
-- Extract itemID from item link
|
||||
@@ -200,6 +237,19 @@ local function GetItemID(link)
|
||||
return tonumber(itemID) or 0
|
||||
end
|
||||
|
||||
-- Get item quality from link color code
|
||||
local function GetQualityFromLink(link)
|
||||
if not link then return 1 end
|
||||
if string.find(link, "|cff9d9d9d") then return 0 end -- Gray
|
||||
if string.find(link, "|cffffffff") then return 1 end -- White
|
||||
if string.find(link, "|cff1eff00") then return 2 end -- Green
|
||||
if string.find(link, "|cff0070dd") then return 3 end -- Blue
|
||||
if string.find(link, "|cffa335ee") then return 4 end -- Purple
|
||||
if string.find(link, "|cffff8000") then return 5 end -- Orange
|
||||
if string.find(link, "|cffe6cc80") then return 6 end -- Red/Artifact
|
||||
return 1
|
||||
end
|
||||
|
||||
-- Extract texture pattern for grouping similar items
|
||||
local function GetTexturePattern(textureName)
|
||||
if not textureName then return "" end
|
||||
@@ -466,24 +516,30 @@ local function AddSortKeys(items)
|
||||
for _, item in ipairs(items) do
|
||||
if item.data and item.data.link then
|
||||
local itemID = GetItemID(item.data.link)
|
||||
local itemName, itemLink, itemRarity, itemLevel, itemCategory, itemType, itemStackCount,
|
||||
itemSubType, itemTexture, itemEquipLoc, itemSellPrice = GetItemInfo(itemID)
|
||||
item.itemName = itemName
|
||||
--addon:Print("itemTexture:%s",itemTexture or 0);
|
||||
--addon:Print("itemEquipLoc:%s",itemEquipLoc or 0);
|
||||
--addon:Print("itemSubType:%s",itemSubType or 0);
|
||||
--addon:Print("itemType:%s",itemType or 0);
|
||||
--addon:Print("itemCategory:%s",itemCategory or 0);
|
||||
--addon:Print("itemLevel:%s",itemLevel or 0);
|
||||
|
||||
-- Use data collected during the initial scan (item.data) for absolute stability
|
||||
local d = item.data
|
||||
local itemName = d.name
|
||||
local itemLink = d.link
|
||||
local itemRarity = d.quality
|
||||
local itemLevel = d.iLevel
|
||||
local itemCategory = d.class
|
||||
local itemType = d.type
|
||||
local itemSubType = d.subclass
|
||||
local itemTexture = d.texture
|
||||
local itemEquipLoc = d.equipLoc
|
||||
local itemStackSize = d.stackSize or 1
|
||||
|
||||
item.itemName = itemName or ""
|
||||
|
||||
if not itemName then
|
||||
-- Skip items that couldn't be loaded
|
||||
-- Skip items that couldn't be loaded (fallback safety)
|
||||
item.sortedClass = 999
|
||||
item.isEquippable = false
|
||||
item.priority = 1000
|
||||
item.equipSlotOrder = 999
|
||||
item.invertedQuality = 0
|
||||
item.invertedItemLevel = 0
|
||||
item.itemName = ""
|
||||
item.sortedSubclass = 999
|
||||
item.subclass = ""
|
||||
item.texturePattern = ""
|
||||
@@ -519,15 +575,14 @@ local function AddSortKeys(items)
|
||||
item.equipSlotOrder = EQUIP_SLOT_ORDER[itemSubType] or 999
|
||||
else
|
||||
item.sortedClass = CATEGORY_ORDER[itemCategory] or 99
|
||||
-- Heuristic: Detect items that should be in the Quest category (priority 11)
|
||||
-- Heuristic: Detect items that should be in the Quest category (priority 7)
|
||||
-- but aren't categorized as such by the game (e.g. some "Manual" items)
|
||||
if item.sortedClass ~= (CATEGORY_ORDER["Quest"] or 99) then
|
||||
local nameLower = item.itemName and string.lower(item.itemName) or ""
|
||||
if item.sortedClass ~= (CATEGORY_ORDER["Quest"] or 7) then
|
||||
local nameLower = string.lower(item.itemName)
|
||||
if string.find(nameLower, "manual") or string.find(nameLower, "quest") then
|
||||
item.sortedClass = 11
|
||||
item.sortedClass = CATEGORY_ORDER["Quest"] or 7
|
||||
elseif IsQuestItemTooltip(item.bagID, item.slot) then
|
||||
-- If name-based heuristic fails, check tooltip
|
||||
item.sortedClass = 11
|
||||
item.sortedClass = CATEGORY_ORDER["Quest"] or 7
|
||||
end
|
||||
end
|
||||
item.equipSlotOrder = 999
|
||||
@@ -535,31 +590,24 @@ local function AddSortKeys(items)
|
||||
|
||||
-- Subclass ordering
|
||||
-- Detect consumable restore/eat/drink tag (if available) BEFORE computing subclass priority
|
||||
item.restoreTag = nil
|
||||
if addon.Modules.Utils and addon.Modules.Utils.GetConsumableRestoreTag then
|
||||
if item.bagID and item.slot then
|
||||
local tag = addon.Modules.Utils:GetConsumableRestoreTag(item.bagID, item.slot)
|
||||
if tag then
|
||||
item.restoreTag = tag
|
||||
end
|
||||
item.restoreTag = GetItemRestoreTagTooltip(item.bagID, item.slot)
|
||||
|
||||
local baseSub = GetSubclassOrder(itemSubType, item.itemName)
|
||||
local function _cons_prk(t)
|
||||
if t == "eat" then return -300 end
|
||||
if t == "drink" then return -200 end
|
||||
if t == "restore" then return -100 end
|
||||
return 0
|
||||
end
|
||||
end
|
||||
local baseSub = GetSubclassOrder(itemSubType, item.name)
|
||||
local function _cons_prk(t)
|
||||
if t == "eat" then return -300 end
|
||||
if t == "drink" then return -200 end
|
||||
if t == "restore" then return -100 end
|
||||
return 0
|
||||
end
|
||||
item.sortedSubclass = _cons_prk(item.restoreTag) + baseSub
|
||||
item.sortedSubclass = _cons_prk(item.restoreTag) + baseSub
|
||||
item.subclass = itemSubType or ""
|
||||
|
||||
-- Quest flags: mark quest items and detect starter/usable states
|
||||
item.isQuest = false
|
||||
item.isQuestStarter = false
|
||||
item.isQuestUsable = false
|
||||
local nameLower = item.itemName and string.lower(item.itemName) or ""
|
||||
if itemCategory == "Quest" or string.find(nameLower, "quest") or (item.data and item.data.class == "Quest") or IsQuestItemTooltip(item.bagID, item.slot) then
|
||||
local nameLower = string.lower(item.itemName)
|
||||
if itemCategory == "Quest" or string.find(nameLower, "quest") or item.data.class == "Quest" or IsQuestItemTooltip(item.bagID, item.slot) then
|
||||
item.isQuest = true
|
||||
if IsQuestItemStarter(item.bagID, item.slot) then item.isQuestStarter = true end
|
||||
if IsQuestItemUsable(item.bagID, item.slot) then item.isQuestUsable = true end
|
||||
@@ -568,7 +616,6 @@ local function AddSortKeys(items)
|
||||
-- Texture pattern for grouping similar items (especially trade goods)
|
||||
item.texturePattern = GetTexturePattern(itemTexture)
|
||||
-- Group Trade Goods: meats (names ending with 'meat') before eggs
|
||||
local nameLower = item.itemName and string.lower(item.itemName) or ""
|
||||
if itemType == "Trade Goods" then
|
||||
if string.find(nameLower, "meat$") then
|
||||
item.texturePattern = "trade_meat"
|
||||
@@ -584,12 +631,9 @@ local function AddSortKeys(items)
|
||||
item.invertedItemID = -tonumber(itemID)
|
||||
|
||||
-- Stack info for reverse stack sorting
|
||||
item.maxStackCount = tonumber(itemStackCount) or 1
|
||||
item.maxStackCount = tonumber(itemStackSize) or 1
|
||||
item.isStackable = item.maxStackCount > 1
|
||||
item.stackCount = tonumber(item.data.count) or 1
|
||||
|
||||
-- Name for alphabetical sorting
|
||||
item.itemName = item.name or ""
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -670,6 +714,13 @@ local function SortItems(items)
|
||||
if ra ~= rb then
|
||||
return ra > rb
|
||||
end
|
||||
elseif a.isQuest ~= b.isQuest then
|
||||
-- If only one is a quest item and they are in the same sortedClass,
|
||||
-- put the quest item first within that class (or just let sortedClass handle it)
|
||||
-- Since we moved Quest to its own class 7, this might not be needed unless
|
||||
-- they are in different classes but one is marked isQuest.
|
||||
-- However, if they have same sortedClass, we want quest items first.
|
||||
return a.isQuest
|
||||
end
|
||||
if a.sortedSubclass ~= b.sortedSubclass then
|
||||
return a.sortedSubclass < b.sortedSubclass
|
||||
@@ -740,6 +791,11 @@ local function CollectItems(bagIDs)
|
||||
-- Get fresh item info with ALL return values
|
||||
local itemID = GetItemID(itemLink)
|
||||
local name, link, quality, iLevel, category, itemType, stackCount, subType, iconTex, equipLoc, sellPrice = GetItemInfo(itemID)
|
||||
|
||||
-- Fallback for quality if GetItemInfo fails (ensures stability)
|
||||
if not quality then
|
||||
quality = GetQualityFromLink(itemLink)
|
||||
end
|
||||
|
||||
sequence = sequence + 1
|
||||
table.insert(items, {
|
||||
@@ -748,14 +804,16 @@ local function CollectItems(bagIDs)
|
||||
sequence = sequence, -- Preserve original order
|
||||
data = {
|
||||
link = itemLink,
|
||||
texture = texture,
|
||||
texture = texture or iconTex,
|
||||
count = itemCount or 1,
|
||||
quality = quality or 0,
|
||||
name = name,
|
||||
iLevel = iLevel,
|
||||
type = itemType,
|
||||
class = category,
|
||||
subclass = subType, -- NOW INCLUDED!
|
||||
subclass = subType,
|
||||
equipLoc = equipLoc,
|
||||
stackSize = stackCount or 1,
|
||||
locked = locked,
|
||||
},
|
||||
quality = quality or 0,
|
||||
@@ -891,8 +949,6 @@ end
|
||||
-- and spilling into previous bags when needed.
|
||||
local function BuildGreyTailPositions(bagIDs, greyCount)
|
||||
local positions = {}
|
||||
local index = 1
|
||||
|
||||
if greyCount <= 0 then return positions end
|
||||
|
||||
-- Order bags: lowest priority first (these are considered "last"),
|
||||
@@ -920,26 +976,42 @@ local function BuildGreyTailPositions(bagIDs, greyCount)
|
||||
end)
|
||||
|
||||
-- Collect tail slots from end to start, spilling to previous bags as needed.
|
||||
local tailSlots = {}
|
||||
for _, info in ipairs(ordered) do
|
||||
for slot = info.numSlots, 1, -1 do
|
||||
if index <= greyCount then
|
||||
positions[index] = { bag = info.bagID, slot = slot }
|
||||
index = index + 1
|
||||
if table.getn(tailSlots) < greyCount then
|
||||
table.insert(tailSlots, { bag = info.bagID, slot = slot })
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if index > greyCount then break end
|
||||
if table.getn(tailSlots) >= greyCount then break end
|
||||
end
|
||||
|
||||
return positions
|
||||
-- STABILITY FIX: Sort the collected tail slots to match the ascending scan order.
|
||||
-- This ensures that identical items don't swap places every pass.
|
||||
-- Ascending order: Priority DESC, BagID ASC, Slot ASC (matching BuildTargetPositions)
|
||||
table.sort(tailSlots, function(a, b)
|
||||
local aPrio = tonumber(addon.Modules.Utils:GetContainerPriority(a.bag)) or 0
|
||||
local bPrio = tonumber(addon.Modules.Utils:GetContainerPriority(b.bag)) or 0
|
||||
if aPrio ~= bPrio then
|
||||
return aPrio > bPrio
|
||||
end
|
||||
if a.bag ~= b.bag then
|
||||
return a.bag < b.bag
|
||||
end
|
||||
return a.slot < b.slot
|
||||
end)
|
||||
|
||||
return tailSlots
|
||||
end
|
||||
|
||||
-- Split a list of collected items into non-greys and greys (quality 0)
|
||||
local function SplitGreyItems(items)
|
||||
local nonGreys, greys = {}, {}
|
||||
for _, item in ipairs(items) do
|
||||
if tonumber(item.quality or 0) == 0 then
|
||||
-- Use same logic as AddSortKeys for determining Junk/Grey status (stability)
|
||||
if tonumber(item.quality or 0) == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link) then
|
||||
table.insert(greys, item)
|
||||
else
|
||||
table.insert(nonGreys, item)
|
||||
@@ -1026,20 +1098,8 @@ function SortEngine:AnalyzeContainer(bagIDs, containerType)
|
||||
|
||||
for i, item in ipairs(greys) do
|
||||
local target = tailPositions[i]
|
||||
-- FIX: Check if grey item is already in a tail position of a valid regular bag
|
||||
local isInTailPosition = false
|
||||
|
||||
-- Check if this grey item is already in the end slots of any valid regular bag
|
||||
for _, bagID in ipairs(validRegularBags) do
|
||||
local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID)
|
||||
if item.bagID == bagID and item.slot >= (numSlots - table.getn(greys) + 1) then
|
||||
isInTailPosition = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Only count as out of place if it's NOT in a tail position AND doesn't match target
|
||||
if target and (item.bagID ~= target.bag or item.slot ~= target.slot) and not isInTailPosition then
|
||||
-- Only count as out of place if it doesn't match the specific stable target
|
||||
if target and (item.bagID ~= target.bag or item.slot ~= target.slot) then
|
||||
totalOutOfPlace = totalOutOfPlace + 1
|
||||
end
|
||||
end
|
||||
@@ -1326,6 +1386,9 @@ end
|
||||
--===========================================================================
|
||||
|
||||
function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sortType)
|
||||
-- Clear property cache at the start of a sort operation
|
||||
self:ClearCache()
|
||||
|
||||
-- Analyze to determine how many passes are needed
|
||||
local analysis = analyzeFunction()
|
||||
|
||||
|
||||
+124
-485
@@ -6,7 +6,19 @@ local addon = Guda
|
||||
local BagFrame = {}
|
||||
addon.Modules.BagFrame = BagFrame
|
||||
|
||||
-- Use centralized frame helpers for section headers and bag parents
|
||||
function BagFrame:GetSectionHeader(index)
|
||||
return Guda_GetSectionHeader("Guda_BagFrame", "Guda_BagFrame_ItemContainer", index)
|
||||
end
|
||||
|
||||
function BagFrame:GetBagParent(bagID)
|
||||
return Guda_GetBagParent("Guda_BagFrame", bagParents, bagID, "Guda_BagFrame_ItemContainer")
|
||||
end
|
||||
|
||||
local currentViewChar = nil -- nil = current character
|
||||
function BagFrame:GetCurrentViewChar()
|
||||
return currentViewChar
|
||||
end
|
||||
local searchText = ""
|
||||
local itemButtons = {}
|
||||
local showKeyring = false -- Toggle for keyring display
|
||||
@@ -109,6 +121,9 @@ end
|
||||
|
||||
-- OnHide
|
||||
function Guda_BagFrame_OnHide(self)
|
||||
-- Close any open dropdown menus when the bag frame is hidden
|
||||
CloseDropDownMenus()
|
||||
|
||||
-- Clean up all buttons when frame is hidden (safe since we're not displaying)
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
@@ -146,21 +161,7 @@ end
|
||||
|
||||
-- Update lock states of existing buttons (lightweight, used during drag)
|
||||
function BagFrame:UpdateLockStates()
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent then
|
||||
local buttons = { bagParent:GetChildren() }
|
||||
for _, button in ipairs(buttons) do
|
||||
if button.hasItem ~= nil and button:IsShown() and button.bagID and button.slotID then
|
||||
-- Get live lock state
|
||||
local _, _, locked = GetContainerItemInfo(button.bagID, button.slotID)
|
||||
-- Update desaturation (gray out locked items)
|
||||
if not button.otherChar and not button.isReadOnly and SetItemButtonDesaturated then
|
||||
SetItemButtonDesaturated(button, locked, 0.5, 0.5, 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Guda_UpdateLockStates(bagParents)
|
||||
end
|
||||
|
||||
-- Update bagline layout (hover option)
|
||||
@@ -365,44 +366,13 @@ function BagFrame:Update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper to get or create section header
|
||||
-- Delegate to centralized helpers
|
||||
function BagFrame:GetSectionHeader(index)
|
||||
local name = "Guda_BagFrame_SectionHeader" .. index
|
||||
local header = getglobal(name)
|
||||
if not header then
|
||||
header = CreateFrame("Frame", name, getglobal("Guda_BagFrame_ItemContainer"))
|
||||
header:SetHeight(20)
|
||||
header:EnableMouse(true)
|
||||
local text = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
text:SetPoint("LEFT", header, "LEFT", 0, 0)
|
||||
header.text = text
|
||||
|
||||
header:SetScript("OnEnter", function()
|
||||
if this.fullName and this.isShortened then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:SetText(this.fullName)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
header:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
end
|
||||
header.inUse = true
|
||||
return header
|
||||
return Guda_GetSectionHeader("Guda_BagFrame", "Guda_BagFrame_ItemContainer", index)
|
||||
end
|
||||
|
||||
-- Helper to get or create bag parent frame
|
||||
function BagFrame:GetBagParent(bagID)
|
||||
local itemContainer = getglobal("Guda_BagFrame_ItemContainer")
|
||||
if not bagParents[bagID] then
|
||||
bagParents[bagID] = CreateFrame("Frame", "Guda_BagFrame_BagParent"..bagID, itemContainer)
|
||||
bagParents[bagID]:SetAllPoints(itemContainer)
|
||||
if bagParents[bagID].SetID then
|
||||
bagParents[bagID]:SetID(bagID)
|
||||
end
|
||||
end
|
||||
return bagParents[bagID]
|
||||
return Guda_GetBagParent("Guda_BagFrame", bagParents, bagID, "Guda_BagFrame_ItemContainer")
|
||||
end
|
||||
|
||||
-- Display items by category
|
||||
@@ -953,113 +923,7 @@ end
|
||||
|
||||
-- Resize frame based on number of rows and columns
|
||||
function BagFrame:ResizeFrame(currentRow, currentCol, columns, overrideHeight)
|
||||
local buttonSize = addon.Modules.DB:GetSetting("iconSize") or addon.Constants.BUTTON_SIZE
|
||||
local spacing = addon.Modules.DB:GetSetting("iconSpacing") or addon.Constants.BUTTON_SPACING
|
||||
|
||||
-- Calculate actual number of rows used
|
||||
local totalRows = (currentRow or 0) + 1
|
||||
|
||||
-- Ensure at least 1 row
|
||||
if totalRows < 1 then
|
||||
totalRows = 1
|
||||
end
|
||||
|
||||
-- Calculate required dimensions based on columns
|
||||
local containerWidth = (columns * (buttonSize + spacing)) + 20
|
||||
local containerHeight = overrideHeight or ((totalRows * (buttonSize + spacing)) + 20)
|
||||
local frameWidth = containerWidth + 20
|
||||
|
||||
-- Check if search bar is visible
|
||||
local showSearchBar = addon.Modules.DB:GetSetting("showSearchBar")
|
||||
if showSearchBar == nil then
|
||||
showSearchBar = true
|
||||
end
|
||||
|
||||
-- Adjust frame height based on search bar visibility
|
||||
-- Footer height varies: more space needed when search bar is visible
|
||||
local titleHeight = 40
|
||||
local searchBarHeight = 30
|
||||
local footerHeight
|
||||
local frameHeight
|
||||
|
||||
local hideFooter = addon.Modules.DB:GetSetting("hideFooter")
|
||||
|
||||
if hideFooter then
|
||||
footerHeight = 10 -- Small padding at bottom
|
||||
frameHeight = containerHeight + titleHeight + (showSearchBar and searchBarHeight or 0) + footerHeight
|
||||
elseif showSearchBar then
|
||||
footerHeight = 55 -- Increased footer height when search bar is visible (toolbar 40px + spacing 15px)
|
||||
frameHeight = containerHeight + titleHeight + searchBarHeight + footerHeight -- 125 total
|
||||
else
|
||||
footerHeight = 45 -- Normal footer height (toolbar 40px + spacing 5px)
|
||||
frameHeight = containerHeight + titleHeight + footerHeight -- 85 total
|
||||
end
|
||||
|
||||
-- Minimum sizes
|
||||
if containerWidth < 200 then
|
||||
containerWidth = 200
|
||||
frameWidth = 220
|
||||
end
|
||||
if containerHeight < 150 then
|
||||
containerHeight = 150
|
||||
end
|
||||
if frameHeight < 250 then
|
||||
frameHeight = 250
|
||||
end
|
||||
|
||||
-- Maximum sizes
|
||||
if containerWidth > 1250 then
|
||||
containerWidth = 1250
|
||||
frameWidth = 1270
|
||||
end
|
||||
if containerHeight > 1000 then
|
||||
containerHeight = 1000
|
||||
end
|
||||
if frameHeight > 1200 then
|
||||
frameHeight = 1200
|
||||
end
|
||||
|
||||
-- Resize frames
|
||||
local bagFrame = getglobal("Guda_BagFrame")
|
||||
local itemContainer = getglobal("Guda_BagFrame_ItemContainer")
|
||||
|
||||
if bagFrame then
|
||||
bagFrame:SetWidth(frameWidth)
|
||||
bagFrame:SetHeight(frameHeight)
|
||||
|
||||
-- Always use BOTTOMRIGHT anchor to make frame grow left
|
||||
bagFrame:ClearAllPoints()
|
||||
|
||||
if addon and addon.Modules and addon.Modules.DB then
|
||||
local pos = addon.Modules.DB:GetSetting("bagFramePosition")
|
||||
-- Only use saved position if it was saved as BOTTOMRIGHT
|
||||
if pos and pos.point == "BOTTOMRIGHT" and pos.x and pos.y then
|
||||
bagFrame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT", pos.x, pos.y)
|
||||
else
|
||||
-- Default position: bottom right corner
|
||||
bagFrame:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -50, 100)
|
||||
end
|
||||
else
|
||||
-- Fallback to default if DB not available
|
||||
bagFrame:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -50, 100)
|
||||
end
|
||||
end
|
||||
|
||||
if itemContainer then
|
||||
itemContainer:SetWidth(containerWidth)
|
||||
itemContainer:SetHeight(containerHeight)
|
||||
end
|
||||
|
||||
-- Resize search bar and toolbar to match container width
|
||||
local searchBar = getglobal("Guda_BagFrame_SearchBar")
|
||||
if searchBar then
|
||||
searchBar:SetWidth(containerWidth)
|
||||
end
|
||||
|
||||
local toolbar = getglobal("Guda_BagFrame_Toolbar")
|
||||
if toolbar then
|
||||
toolbar:SetWidth(containerWidth)
|
||||
end
|
||||
return Guda_ResizeFrame("Guda_BagFrame", "Guda_BagFrame_ItemContainer", currentRow, currentCol, columns, overrideHeight)
|
||||
end
|
||||
|
||||
-- Check if search is currently active
|
||||
@@ -1069,74 +933,8 @@ end
|
||||
|
||||
-- Check if item passes search filter (pfUI style)
|
||||
function BagFrame:PassesSearchFilter(itemData)
|
||||
-- If no search text, everything matches
|
||||
if not self:IsSearchActive() then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Empty slots don't match when searching (pfUI style - they get dimmed)
|
||||
if not itemData then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Get item name from itemData.name or parse from link
|
||||
local itemName = itemData.name
|
||||
if not itemName and itemData.link then
|
||||
-- Parse name from item link: |cffffffff|Hitem:...|h[Item Name]|h|r
|
||||
local _, _, name = string.find(itemData.link, "%[(.+)%]")
|
||||
itemName = name
|
||||
if not self.warnedAboutParsing then
|
||||
addon:Print("DEBUG: Had to parse item name from link: " .. (itemName or "FAILED"))
|
||||
self.warnedAboutParsing = true
|
||||
end
|
||||
end
|
||||
|
||||
if not itemName then
|
||||
if not self.warnedAboutNoName then
|
||||
addon:Print("DEBUG: Item has no name and no link! texture = " .. tostring(itemData.texture))
|
||||
self.warnedAboutNoName = true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Case-insensitive search in item name
|
||||
local search = string.lower(searchText)
|
||||
|
||||
-- Advanced search (pfUI style categories)
|
||||
if string.sub(search, 1, 1) == "~" then
|
||||
local category = string.sub(search, 2)
|
||||
local itemType = itemData.class or ""
|
||||
local itemQuality = itemData.quality or -1
|
||||
|
||||
if category == "equipment" or category == "armor" or category == "weapon" then
|
||||
if itemType == "Armor" or itemType == "Weapon" then return true end
|
||||
elseif category == "consumable" then
|
||||
if itemType == "Consumable" then return true end
|
||||
elseif category == "tradegoods" or category == "trades" then
|
||||
if itemType == "Trade Goods" then return true end
|
||||
elseif category == "quest" then
|
||||
local isQuest, isQuestStarter = Guda_GetQuestInfo(itemData.bagID, itemData.slotID, itemData.isBank)
|
||||
if isQuest or isQuestStarter or itemType == "Quest" then return true end
|
||||
elseif category == "reagent" then
|
||||
if itemType == "Reagent" then return true end
|
||||
elseif category == "common" then if itemQuality == 1 then return true end
|
||||
elseif category == "uncommon" then if itemQuality == 2 then return true end
|
||||
elseif category == "rare" then if itemQuality == 3 then return true end
|
||||
elseif category == "epic" then if itemQuality == 4 then return true end
|
||||
elseif category == "legendary" then if itemQuality == 5 then return true end
|
||||
end
|
||||
end
|
||||
|
||||
itemName = string.lower(itemName)
|
||||
-- Check if item name contains search text
|
||||
local matches = string.find(itemName, search, 1, true) ~= nil
|
||||
|
||||
-- Debug: print first match found
|
||||
if matches and not self.foundFirstMatch then
|
||||
self.foundFirstMatch = true
|
||||
end
|
||||
|
||||
return matches
|
||||
if not self:IsSearchActive() then return true end
|
||||
return Guda_PassesSearchFilter(itemData, searchText)
|
||||
end
|
||||
|
||||
function BagFrame:UpdateMoney()
|
||||
@@ -1405,119 +1203,15 @@ function Guda_BagFrame_MoneyOnEnter(self)
|
||||
end
|
||||
|
||||
-- Dropdown management
|
||||
local characterDropdown = nil
|
||||
local bankDropdown = nil
|
||||
|
||||
-- Toggle character dropdown
|
||||
function Guda_BagFrame_ToggleCharacterDropdown(button)
|
||||
-- Hide bank dropdown if it's shown
|
||||
if bankDropdown and bankDropdown:IsShown() then
|
||||
bankDropdown:Hide()
|
||||
end
|
||||
|
||||
if characterDropdown and characterDropdown:IsShown() then
|
||||
characterDropdown:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
if not characterDropdown then
|
||||
-- Create dropdown frame
|
||||
characterDropdown = CreateFrame("Frame", "Guda_CharacterDropdown", UIParent)
|
||||
characterDropdown:SetFrameStrata("DIALOG")
|
||||
characterDropdown:SetWidth(200)
|
||||
characterDropdown:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
|
||||
tile = true,
|
||||
tileSize = 16,
|
||||
edgeSize = 16,
|
||||
insets = { left = 4, right = 4, top = 4, bottom = 4 }
|
||||
})
|
||||
characterDropdown:SetBackdropColor(0, 0, 0, 0.95)
|
||||
characterDropdown:EnableMouse(true)
|
||||
characterDropdown:Hide()
|
||||
|
||||
characterDropdown.buttons = {}
|
||||
end
|
||||
|
||||
-- Position dropdown below the button
|
||||
characterDropdown:ClearAllPoints()
|
||||
characterDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2)
|
||||
|
||||
-- Clear existing buttons
|
||||
for _, btn in ipairs(characterDropdown.buttons) do
|
||||
btn:Hide()
|
||||
end
|
||||
characterDropdown.buttons = {}
|
||||
|
||||
-- Get all characters on current realm
|
||||
local chars = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
|
||||
-- Add "Current Character" option at the top
|
||||
local yOffset = -8
|
||||
local currentCharButton = CreateFrame("Button", nil, characterDropdown)
|
||||
currentCharButton:SetWidth(188)
|
||||
currentCharButton:SetHeight(20)
|
||||
currentCharButton:SetPoint("TOP", characterDropdown, "TOP", 0, yOffset)
|
||||
|
||||
-- Button background on hover
|
||||
local currentCharBg = currentCharButton:CreateTexture(nil, "BACKGROUND")
|
||||
currentCharBg:SetAllPoints()
|
||||
currentCharBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
currentCharBg:SetBlendMode("ADD")
|
||||
currentCharBg:SetAlpha(0)
|
||||
|
||||
-- Button text
|
||||
local currentCharText = currentCharButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
||||
currentCharText:SetPoint("LEFT", currentCharButton, "LEFT", 8, 0)
|
||||
currentCharText:SetText("Characters")
|
||||
|
||||
-- Button scripts
|
||||
currentCharButton:SetScript("OnEnter", function()
|
||||
currentCharBg:SetAlpha(0.3)
|
||||
end)
|
||||
currentCharButton:SetScript("OnLeave", function()
|
||||
currentCharBg:SetAlpha(0)
|
||||
end)
|
||||
currentCharButton:SetScript("OnClick", function()
|
||||
addon.Modules.BagFrame:ShowCurrentCharacter()
|
||||
characterDropdown:Hide()
|
||||
end)
|
||||
|
||||
table.insert(characterDropdown.buttons, currentCharButton)
|
||||
yOffset = yOffset - 20
|
||||
|
||||
-- Add separator
|
||||
local separator = characterDropdown:CreateTexture(nil, "ARTWORK")
|
||||
separator:SetHeight(1)
|
||||
separator:SetWidth(180)
|
||||
separator:SetPoint("TOP", characterDropdown, "TOP", 0, yOffset)
|
||||
separator:SetTexture(1, 1, 1, 0.2)
|
||||
yOffset = yOffset - 4
|
||||
|
||||
-- Get current player's full name for comparison
|
||||
local function Guda_BagCharacterMenu_Initialize()
|
||||
local characters = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
local info
|
||||
local currentPlayerFullName = addon.Modules.DB:GetPlayerFullName()
|
||||
local currentViewChar = addon.Modules.BagFrame:GetCurrentViewChar()
|
||||
|
||||
-- Add character buttons
|
||||
for _, char in ipairs(chars) do
|
||||
-- Capture variables in local scope for closure
|
||||
for i, char in ipairs(characters) do
|
||||
local charFullName = char.fullName
|
||||
local charName = char.name
|
||||
local charMoney = char.money or 0
|
||||
local charClassToken = char.classToken
|
||||
local isCurrentChar = (charFullName == currentPlayerFullName)
|
||||
|
||||
local charButton = CreateFrame("Button", nil, characterDropdown)
|
||||
charButton:SetWidth(188)
|
||||
charButton:SetHeight(20)
|
||||
charButton:SetPoint("TOP", characterDropdown, "TOP", 0, yOffset)
|
||||
|
||||
-- Button background on hover
|
||||
local charBg = charButton:CreateTexture(nil, "BACKGROUND")
|
||||
charBg:SetAllPoints()
|
||||
charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
charBg:SetBlendMode("ADD")
|
||||
charBg:SetAlpha(0)
|
||||
|
||||
-- Get class color
|
||||
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
|
||||
@@ -1526,169 +1220,115 @@ function Guda_BagFrame_ToggleCharacterDropdown(button)
|
||||
r, g, b = classColor.r, classColor.g, classColor.b
|
||||
end
|
||||
|
||||
-- Button text
|
||||
local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
||||
charText:SetPoint("LEFT", charButton, "LEFT", 8, 0)
|
||||
charText:SetText(charName)
|
||||
charText:SetTextColor(r, g, b)
|
||||
-- Create colored name
|
||||
local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b)
|
||||
|
||||
-- Money text
|
||||
local moneyText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
moneyText:SetPoint("RIGHT", charButton, "RIGHT", -8, 0)
|
||||
moneyText:SetText(addon.Modules.Utils:FormatMoney(charMoney))
|
||||
moneyText:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
-- Button scripts
|
||||
charButton:SetScript("OnEnter", function()
|
||||
charBg:SetAlpha(0.3)
|
||||
end)
|
||||
charButton:SetScript("OnLeave", function()
|
||||
charBg:SetAlpha(0)
|
||||
end)
|
||||
charButton:SetScript("OnClick", function()
|
||||
if charFullName then
|
||||
if isCurrentChar then
|
||||
-- Clicking current character - show current live view
|
||||
addon.Modules.BagFrame:ShowCurrentCharacter()
|
||||
else
|
||||
-- Clicking different character - show their stored bags
|
||||
addon.Modules.BagFrame:ShowCharacter(charFullName)
|
||||
end
|
||||
characterDropdown:Hide()
|
||||
info = {}
|
||||
info.text = coloredName
|
||||
info.func = function()
|
||||
if charFullName == currentPlayerFullName then
|
||||
addon.Modules.BagFrame:ShowCurrentCharacter()
|
||||
else
|
||||
addon:Print("Error: Character fullName is nil")
|
||||
addon.Modules.BagFrame:ShowCharacter(charFullName)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(characterDropdown.buttons, charButton)
|
||||
yOffset = yOffset - 20
|
||||
end
|
||||
info.checked = (currentViewChar == charFullName or (not currentViewChar and charFullName == currentPlayerFullName))
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end
|
||||
|
||||
-- Set dropdown height based on content
|
||||
characterDropdown:SetHeight(math.abs(yOffset) + 8)
|
||||
|
||||
-- Show dropdown
|
||||
characterDropdown:Show()
|
||||
end
|
||||
|
||||
-- Hide dropdown when clicking elsewhere
|
||||
local function HideCharacterDropdown()
|
||||
if characterDropdown then
|
||||
characterDropdown:Hide()
|
||||
-- Toggle character dropdown
|
||||
function Guda_BagFrame_ToggleCharacterDropdown(button)
|
||||
local menuFrame = getglobal("Guda_BagCharacterMenu")
|
||||
if not menuFrame then
|
||||
menuFrame = CreateFrame("Frame", "Guda_BagCharacterMenu", UIParent, "UIDropDownMenuTemplate")
|
||||
end
|
||||
UIDropDownMenu_Initialize(menuFrame, Guda_BagCharacterMenu_Initialize, "MENU")
|
||||
ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0)
|
||||
end
|
||||
|
||||
-- Mailbox character dropdown
|
||||
local function Guda_BagMailboxMenu_Initialize()
|
||||
local characters = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
local info
|
||||
|
||||
for i, char in ipairs(characters) do
|
||||
local charFullName = char.fullName
|
||||
local charClassToken = char.classToken
|
||||
|
||||
-- Get class color
|
||||
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
|
||||
local r, g, b = 1, 1, 1
|
||||
if classColor then
|
||||
r, g, b = classColor.r, classColor.g, classColor.b
|
||||
end
|
||||
|
||||
-- Create colored name
|
||||
local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b)
|
||||
|
||||
info = {}
|
||||
info.text = coloredName
|
||||
info.func = function()
|
||||
addon.Modules.MailboxFrame:ShowCharacter(charFullName)
|
||||
if not Guda_MailboxFrame:IsShown() then
|
||||
Guda_MailboxFrame:Show()
|
||||
end
|
||||
end
|
||||
local mailboxViewChar = addon.Modules.MailboxFrame:GetCurrentViewChar()
|
||||
info.checked = (mailboxViewChar == charFullName or (not mailboxViewChar and charFullName == addon.Modules.DB:GetPlayerFullName()))
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end
|
||||
end
|
||||
|
||||
-- Toggle mail dropdown
|
||||
function Guda_BagFrame_ToggleMailDropdown(button)
|
||||
local menuFrame = getglobal("Guda_BagMailboxMenu")
|
||||
if not menuFrame then
|
||||
menuFrame = CreateFrame("Frame", "Guda_BagMailboxMenu", UIParent, "UIDropDownMenuTemplate")
|
||||
end
|
||||
UIDropDownMenu_Initialize(menuFrame, Guda_BagMailboxMenu_Initialize, "MENU")
|
||||
ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0)
|
||||
end
|
||||
|
||||
-- Bank character dropdown
|
||||
local function Guda_BagBankMenu_Initialize()
|
||||
local characters = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
local info
|
||||
|
||||
for i, char in ipairs(characters) do
|
||||
local charFullName = char.fullName
|
||||
local charClassToken = char.classToken
|
||||
|
||||
-- Get class color
|
||||
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
|
||||
local r, g, b = 1, 1, 1
|
||||
if classColor then
|
||||
r, g, b = classColor.r, classColor.g, classColor.b
|
||||
end
|
||||
|
||||
-- Create colored name
|
||||
local charName = char.name
|
||||
local coloredName = addon.Modules.Utils:ColorText(charName, r, g, b)
|
||||
|
||||
info = {}
|
||||
info.text = coloredName
|
||||
info.func = function()
|
||||
Guda_BagFrame_ShowCharacterBank(charFullName, charName)
|
||||
end
|
||||
local bankViewChar = addon.Modules.BankFrame:GetCurrentViewChar()
|
||||
info.checked = (bankViewChar == charFullName or (not bankViewChar and charFullName == addon.Modules.DB:GetPlayerFullName()))
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end
|
||||
end
|
||||
|
||||
-- Toggle bank dropdown
|
||||
function Guda_BagFrame_ToggleBankDropdown(button)
|
||||
-- Hide character dropdown if it's shown
|
||||
if characterDropdown and characterDropdown:IsShown() then
|
||||
characterDropdown:Hide()
|
||||
local menuFrame = getglobal("Guda_BagBankMenu")
|
||||
if not menuFrame then
|
||||
menuFrame = CreateFrame("Frame", "Guda_BagBankMenu", UIParent, "UIDropDownMenuTemplate")
|
||||
end
|
||||
|
||||
if bankDropdown and bankDropdown:IsShown() then
|
||||
bankDropdown:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
if not bankDropdown then
|
||||
-- Create dropdown frame
|
||||
bankDropdown = CreateFrame("Frame", "Guda_BankDropdown", UIParent)
|
||||
bankDropdown:SetFrameStrata("DIALOG")
|
||||
bankDropdown:SetWidth(200)
|
||||
bankDropdown:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
|
||||
tile = true,
|
||||
tileSize = 16,
|
||||
edgeSize = 16,
|
||||
insets = { left = 4, right = 4, top = 4, bottom = 4 }
|
||||
})
|
||||
bankDropdown:SetBackdropColor(0, 0, 0, 0.95)
|
||||
bankDropdown:EnableMouse(true)
|
||||
bankDropdown:Hide()
|
||||
|
||||
bankDropdown.buttons = {}
|
||||
end
|
||||
|
||||
-- Position dropdown below the button
|
||||
bankDropdown:ClearAllPoints()
|
||||
bankDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2)
|
||||
-- Clear existing buttons
|
||||
for _, btn in ipairs(bankDropdown.buttons) do
|
||||
btn:Hide()
|
||||
end
|
||||
bankDropdown.buttons = {}
|
||||
|
||||
-- Get all characters on current realm
|
||||
local chars = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
|
||||
local yOffset = -8
|
||||
|
||||
-- Add character buttons
|
||||
for _, char in ipairs(chars) do
|
||||
-- Capture variables in local scope for closure
|
||||
local charFullName = char.fullName
|
||||
local charName = char.name
|
||||
local charMoney = char.money or 0
|
||||
local charClassToken = char.classToken
|
||||
|
||||
local charButton = CreateFrame("Button", nil, bankDropdown)
|
||||
charButton:SetWidth(188)
|
||||
charButton:SetHeight(20)
|
||||
charButton:SetPoint("TOP", bankDropdown, "TOP", 0, yOffset)
|
||||
|
||||
-- Button background on hover
|
||||
local charBg = charButton:CreateTexture(nil, "BACKGROUND")
|
||||
charBg:SetAllPoints()
|
||||
charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
charBg:SetBlendMode("ADD")
|
||||
charBg:SetAlpha(0)
|
||||
|
||||
-- Get class color
|
||||
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
|
||||
local r, g, b = 1, 1, 1
|
||||
if classColor then
|
||||
r, g, b = classColor.r, classColor.g, classColor.b
|
||||
end
|
||||
|
||||
-- Button text
|
||||
local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
||||
charText:SetPoint("LEFT", charButton, "LEFT", 8, 0)
|
||||
charText:SetText(charName)
|
||||
charText:SetTextColor(r, g, b)
|
||||
|
||||
-- Money text
|
||||
local moneyText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
moneyText:SetPoint("RIGHT", charButton, "RIGHT", -8, 0)
|
||||
moneyText:SetText(addon.Modules.Utils:FormatMoney(charMoney))
|
||||
moneyText:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
-- Button scripts
|
||||
charButton:SetScript("OnEnter", function()
|
||||
charBg:SetAlpha(0.3)
|
||||
end)
|
||||
charButton:SetScript("OnLeave", function()
|
||||
charBg:SetAlpha(0)
|
||||
end)
|
||||
charButton:SetScript("OnClick", function()
|
||||
if charFullName then
|
||||
-- Show bank for this character
|
||||
Guda_BagFrame_ShowCharacterBank(charFullName, charName)
|
||||
bankDropdown:Hide()
|
||||
else
|
||||
addon:Print("Error: Character fullName is nil")
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(bankDropdown.buttons, charButton)
|
||||
yOffset = yOffset - 20
|
||||
end
|
||||
|
||||
-- Set dropdown height based on content
|
||||
bankDropdown:SetHeight(math.abs(yOffset) + 8)
|
||||
|
||||
-- Show dropdown
|
||||
bankDropdown:Show()
|
||||
UIDropDownMenu_Initialize(menuFrame, Guda_BagBankMenu_Initialize, "MENU")
|
||||
ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0)
|
||||
end
|
||||
|
||||
-- Show character's bank
|
||||
@@ -2567,7 +2207,6 @@ function BagFrame:Initialize()
|
||||
if bagFrame then
|
||||
local originalOnMouseDown = bagFrame:GetScript("OnMouseDown")
|
||||
bagFrame:SetScript("OnMouseDown", function()
|
||||
HideCharacterDropdown()
|
||||
if originalOnMouseDown then
|
||||
originalOnMouseDown()
|
||||
end
|
||||
|
||||
+46
-9
@@ -248,6 +248,52 @@
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Mail Button (header left, next to bank) -->
|
||||
<Button name="$parent_MailButton">
|
||||
<Size>
|
||||
<AbsDimension x="24" y="24"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parent_BankButton">
|
||||
<Offset>
|
||||
<AbsDimension x="2" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parent_Icon">
|
||||
<Size>
|
||||
<AbsDimension x="20" y="20"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
<Color r="0.8" g="0.8" b="0.8"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
local icon = getglobal(this:GetName().."_Icon")
|
||||
if icon then
|
||||
icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\Envelope")
|
||||
end
|
||||
</OnLoad>
|
||||
<OnClick>
|
||||
Guda_BagFrame_ToggleMailDropdown(this)
|
||||
</OnClick>
|
||||
<OnEnter>
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:SetText("View Mailbox")
|
||||
GameTooltip:Show()
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
GameTooltip:Hide()
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Toolbar Container (for money tooltip and bag slots) -->
|
||||
<Frame name="$parent_Toolbar" enableMouse="true">
|
||||
<Size>
|
||||
@@ -654,15 +700,6 @@
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="BOTTOM" relativeTo="$parent_SearchBar" x="0" y="-5"/>
|
||||
</Anchors>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" tile="true">
|
||||
<BackgroundInsets>
|
||||
<AbsInset left="0" right="0" top="0" bottom="0"/>
|
||||
</BackgroundInsets>
|
||||
<TileSize>
|
||||
<AbsValue val="16"/>
|
||||
</TileSize>
|
||||
<Color r="0" g="0" b="0" a="0.5"/>
|
||||
</Backdrop>
|
||||
<!-- Note: with enableMouse=false, OnMouseDown/OnReceiveDrag here would not fire;
|
||||
child item buttons will handle mouse events directly. -->
|
||||
</Frame>
|
||||
|
||||
+47
-195
@@ -7,6 +7,9 @@ local BankFrame = {}
|
||||
addon.Modules.BankFrame = BankFrame
|
||||
|
||||
local currentViewChar = nil
|
||||
function BankFrame:GetCurrentViewChar()
|
||||
return currentViewChar
|
||||
end
|
||||
local searchText = ""
|
||||
local isReadOnlyMode = false -- Track if viewing saved bank (read-only) or live bank (interactive)
|
||||
local hiddenBankBags = {} -- Track which bank bags are hidden (bagID -> true/false)
|
||||
@@ -78,6 +81,18 @@ function Guda_BankFrame_OnShow(self)
|
||||
BankFrame:Update()
|
||||
end
|
||||
|
||||
-- OnHide
|
||||
function Guda_BankFrame_OnHide(self)
|
||||
-- Close any open dropdown menus when the bank frame is hidden
|
||||
CloseDropDownMenus()
|
||||
|
||||
-- Close the actual Blizzard bank too
|
||||
local blizzardBankFrame = getglobal("BankFrame")
|
||||
if blizzardBankFrame and blizzardBankFrame:IsShown() then
|
||||
CloseBankFrame()
|
||||
end
|
||||
end
|
||||
|
||||
-- Toggle visibility
|
||||
function BankFrame:Toggle()
|
||||
if Guda_BankFrame:IsShown() then
|
||||
@@ -103,21 +118,7 @@ end
|
||||
|
||||
-- Update lock states of existing buttons (lightweight, used during drag)
|
||||
function BankFrame:UpdateLockStates()
|
||||
for _, bankBagParent in pairs(bankBagParents) do
|
||||
if bankBagParent then
|
||||
local buttons = { bankBagParent:GetChildren() }
|
||||
for _, button in ipairs(buttons) do
|
||||
if button.hasItem ~= nil and button:IsShown() and button.bagID and button.slotID then
|
||||
-- Get live lock state
|
||||
local _, _, locked = GetContainerItemInfo(button.bagID, button.slotID)
|
||||
-- Update desaturation (gray out locked items)
|
||||
if not button.otherChar and not button.isReadOnly and SetItemButtonDesaturated then
|
||||
SetItemButtonDesaturated(button, locked, 0.5, 0.5, 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Guda_UpdateLockStates(bankBagParents)
|
||||
end
|
||||
|
||||
-- Update display
|
||||
@@ -217,44 +218,13 @@ function BankFrame:Update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper to get or create section header
|
||||
-- Use centralized frame helpers for section headers and bag parents
|
||||
function BankFrame:GetSectionHeader(index)
|
||||
local name = "Guda_BankFrame_SectionHeader" .. index
|
||||
local header = getglobal(name)
|
||||
if not header then
|
||||
header = CreateFrame("Frame", name, getglobal("Guda_BankFrame_ItemContainer"))
|
||||
header:SetHeight(20)
|
||||
header:EnableMouse(true)
|
||||
local text = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
text:SetPoint("LEFT", header, "LEFT", 0, 0)
|
||||
header.text = text
|
||||
|
||||
header:SetScript("OnEnter", function()
|
||||
if this.fullName and this.isShortened then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:SetText(this.fullName)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
header:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
end
|
||||
header.inUse = true
|
||||
return header
|
||||
return Guda_GetSectionHeader("Guda_BankFrame", "Guda_BankFrame_ItemContainer", index)
|
||||
end
|
||||
|
||||
-- Helper to get or create bank bag parent frame
|
||||
function BankFrame:GetBagParent(bagID)
|
||||
local itemContainer = getglobal("Guda_BankFrame_ItemContainer")
|
||||
if not bankBagParents[bagID] then
|
||||
bankBagParents[bagID] = CreateFrame("Frame", "Guda_BankFrame_BagParent"..bagID, itemContainer)
|
||||
bankBagParents[bagID]:SetAllPoints(itemContainer)
|
||||
if bankBagParents[bagID].SetID then
|
||||
bankBagParents[bagID]:SetID(bagID)
|
||||
end
|
||||
end
|
||||
return bankBagParents[bagID]
|
||||
return Guda_GetBagParent("Guda_BankFrame", bankBagParents, bagID, "Guda_BankFrame_ItemContainer")
|
||||
end
|
||||
|
||||
-- Display items by category
|
||||
@@ -1018,59 +988,8 @@ end
|
||||
|
||||
-- Check if item passes search filter (pfUI style)
|
||||
function BankFrame:PassesSearchFilter(itemData)
|
||||
-- If no search text, everything matches
|
||||
if not self:IsSearchActive() then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Empty slots don't match when searching (pfUI style - they get dimmed)
|
||||
if not itemData then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Get item name from itemData.name or parse from link
|
||||
local itemName = itemData.name
|
||||
if not itemName and itemData.link then
|
||||
-- Parse name from item link: |cffffffff|Hitem:...|h[Item Name]|h|r
|
||||
local _, _, name = string.find(itemData.link, "%[(.+)%]")
|
||||
itemName = name
|
||||
end
|
||||
|
||||
if not itemName then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Case-insensitive search in item name
|
||||
local search = string.lower(searchText)
|
||||
|
||||
-- Advanced search (pfUI style categories)
|
||||
if string.sub(search, 1, 1) == "~" then
|
||||
local category = string.sub(search, 2)
|
||||
local itemType = itemData.class or ""
|
||||
local itemQuality = itemData.quality or -1
|
||||
|
||||
if category == "equipment" or category == "armor" or category == "weapon" then
|
||||
if itemType == "Armor" or itemType == "Weapon" then return true end
|
||||
elseif category == "consumable" then
|
||||
if itemType == "Consumable" then return true end
|
||||
elseif category == "tradegoods" or category == "trades" then
|
||||
if itemType == "Trade Goods" then return true end
|
||||
elseif category == "quest" then
|
||||
local isQuest, isQuestStarter = Guda_GetQuestInfo(itemData.bagID, itemData.slotID, itemData.isBank)
|
||||
if isQuest or isQuestStarter or itemType == "Quest" then return true end
|
||||
elseif category == "reagent" then
|
||||
if itemType == "Reagent" then return true end
|
||||
elseif category == "common" then if itemQuality == 1 then return true end
|
||||
elseif category == "uncommon" then if itemQuality == 2 then return true end
|
||||
elseif category == "rare" then if itemQuality == 3 then return true end
|
||||
elseif category == "epic" then if itemQuality == 4 then return true end
|
||||
elseif category == "legendary" then if itemQuality == 5 then return true end
|
||||
end
|
||||
end
|
||||
|
||||
itemName = string.lower(itemName)
|
||||
-- Check if item name contains search text
|
||||
return string.find(itemName, search, 1, true) ~= nil
|
||||
if not self:IsSearchActive() then return true end
|
||||
return Guda_PassesSearchFilter(itemData, searchText)
|
||||
end
|
||||
|
||||
-- Search changed handler
|
||||
@@ -1829,69 +1748,16 @@ function Guda_BankFrame_ClearBagButtonHighlight()
|
||||
end
|
||||
|
||||
-- Bank character dropdown (similar to BagFrame's bank dropdown)
|
||||
local bankCharDropdown
|
||||
local function Guda_BankCharacterMenu_Initialize()
|
||||
local characters = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
local info
|
||||
local currentPlayerFullName = addon.Modules.DB:GetPlayerFullName()
|
||||
local currentViewChar = addon.Modules.BankFrame:GetCurrentViewChar()
|
||||
|
||||
function Guda_BankFrame_ToggleBankDropdown(button)
|
||||
if bankCharDropdown and bankCharDropdown:IsShown() then
|
||||
bankCharDropdown:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
if not bankCharDropdown then
|
||||
-- Create dropdown frame
|
||||
bankCharDropdown = CreateFrame("Frame", "Guda_BankCharDropdown", UIParent)
|
||||
bankCharDropdown:SetFrameStrata("DIALOG")
|
||||
bankCharDropdown:SetWidth(200)
|
||||
bankCharDropdown:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
|
||||
tile = true,
|
||||
tileSize = 16,
|
||||
edgeSize = 16,
|
||||
insets = { left = 4, right = 4, top = 4, bottom = 4 }
|
||||
})
|
||||
bankCharDropdown:SetBackdropColor(0, 0, 0, 0.95)
|
||||
bankCharDropdown:EnableMouse(true)
|
||||
bankCharDropdown:Hide()
|
||||
|
||||
bankCharDropdown.buttons = {}
|
||||
end
|
||||
|
||||
-- Position dropdown below the button
|
||||
bankCharDropdown:ClearAllPoints()
|
||||
bankCharDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2)
|
||||
|
||||
-- Clear existing buttons
|
||||
for _, btn in ipairs(bankCharDropdown.buttons) do
|
||||
btn:Hide()
|
||||
end
|
||||
bankCharDropdown.buttons = {}
|
||||
|
||||
-- Get all characters on current realm
|
||||
local chars = addon.Modules.DB:GetAllCharacters(false, true)
|
||||
|
||||
local yOffset = -8
|
||||
|
||||
-- Add character buttons
|
||||
for _, char in ipairs(chars) do
|
||||
-- Capture variables in local scope for closure
|
||||
for i, char in ipairs(characters) do
|
||||
local charFullName = char.fullName
|
||||
local charName = char.name
|
||||
local charMoney = char.money or 0
|
||||
local charClassToken = char.classToken
|
||||
|
||||
local charButton = CreateFrame("Button", nil, bankCharDropdown)
|
||||
charButton:SetWidth(188)
|
||||
charButton:SetHeight(20)
|
||||
charButton:SetPoint("TOP", bankCharDropdown, "TOP", 0, yOffset)
|
||||
|
||||
-- Button background on hover
|
||||
local charBg = charButton:CreateTexture(nil, "BACKGROUND")
|
||||
charBg:SetAllPoints()
|
||||
charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
charBg:SetBlendMode("ADD")
|
||||
charBg:SetAlpha(0)
|
||||
|
||||
|
||||
-- Get class color
|
||||
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
|
||||
local r, g, b = 1, 1, 1
|
||||
@@ -1899,44 +1765,30 @@ function Guda_BankFrame_ToggleBankDropdown(button)
|
||||
r, g, b = classColor.r, classColor.g, classColor.b
|
||||
end
|
||||
|
||||
-- Button text
|
||||
local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
||||
charText:SetPoint("LEFT", charButton, "LEFT", 8, 0)
|
||||
charText:SetText(charName)
|
||||
charText:SetTextColor(r, g, b)
|
||||
-- Create colored name
|
||||
local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b)
|
||||
|
||||
-- Money text
|
||||
local moneyText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
moneyText:SetPoint("RIGHT", charButton, "RIGHT", -8, 0)
|
||||
moneyText:SetText(addon.Modules.Utils:FormatMoney(charMoney))
|
||||
moneyText:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
-- Button scripts
|
||||
charButton:SetScript("OnEnter", function()
|
||||
charBg:SetAlpha(0.3)
|
||||
end)
|
||||
charButton:SetScript("OnLeave", function()
|
||||
charBg:SetAlpha(0)
|
||||
end)
|
||||
charButton:SetScript("OnClick", function()
|
||||
if charFullName then
|
||||
-- Show bank for this character using the BankFrame module
|
||||
addon.Modules.BankFrame:ShowCharacter(charFullName)
|
||||
bankCharDropdown:Hide()
|
||||
info = {}
|
||||
info.text = coloredName
|
||||
info.func = function()
|
||||
if charFullName == currentPlayerFullName then
|
||||
addon.Modules.BankFrame:ShowCurrentCharacter()
|
||||
else
|
||||
addon:Print("Error: Character fullName is nil")
|
||||
addon.Modules.BankFrame:ShowCharacter(charFullName)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(bankCharDropdown.buttons, charButton)
|
||||
yOffset = yOffset - 20
|
||||
end
|
||||
info.checked = (currentViewChar == charFullName or (not currentViewChar and charFullName == currentPlayerFullName))
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end
|
||||
end
|
||||
|
||||
-- Set dropdown height based on content
|
||||
bankCharDropdown:SetHeight(math.abs(yOffset) + 8)
|
||||
|
||||
-- Show dropdown
|
||||
bankCharDropdown:Show()
|
||||
function Guda_BankFrame_ToggleBankDropdown(button)
|
||||
local menuFrame = getglobal("Guda_BankCharacterMenu")
|
||||
if not menuFrame then
|
||||
menuFrame = CreateFrame("Frame", "Guda_BankCharacterMenu", UIParent, "UIDropDownMenuTemplate")
|
||||
end
|
||||
UIDropDownMenu_Initialize(menuFrame, Guda_BankCharacterMenu_Initialize, "MENU")
|
||||
ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0)
|
||||
end
|
||||
|
||||
-- Drag handlers for bank bag slots
|
||||
|
||||
+1
-14
@@ -573,15 +573,6 @@
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="BOTTOM" relativeTo="$parent_SearchBar" x="0" y="-5"/>
|
||||
</Anchors>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" tile="true">
|
||||
<BackgroundInsets>
|
||||
<AbsInset left="0" right="0" top="0" bottom="10"/>
|
||||
</BackgroundInsets>
|
||||
<TileSize>
|
||||
<AbsValue val="16"/>
|
||||
</TileSize>
|
||||
<Color r="0" g="0" b="0" a="0.5"/>
|
||||
</Backdrop>
|
||||
<Scripts>
|
||||
<OnMouseDown>
|
||||
-- Clear search focus when clicking in item area
|
||||
@@ -602,11 +593,7 @@
|
||||
Guda_BankFrame_OnShow(this)
|
||||
</OnShow>
|
||||
<OnHide>
|
||||
Guda_BankFrame:Hide()
|
||||
-- Close the actual Blizzard bank too
|
||||
if BankFrame and BankFrame:IsShown() then
|
||||
CloseBankFrame()
|
||||
end
|
||||
Guda_BankFrame_OnHide(this)
|
||||
</OnHide>
|
||||
<OnMouseDown>
|
||||
-- Clear search box focus when clicking on bank frame
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
-- FrameHelpers: central utilities for frame headers and bag parents
|
||||
local addon = Guda
|
||||
|
||||
local FrameHelpers = {}
|
||||
addon.Modules.FrameHelpers = FrameHelpers
|
||||
|
||||
-- Create or return a section header for a given frame prefix and container
|
||||
function Guda_GetSectionHeader(framePrefix, containerName, index)
|
||||
local name = framePrefix .. "_SectionHeader" .. index
|
||||
local header = getglobal(name)
|
||||
if not header then
|
||||
local container = getglobal(containerName)
|
||||
header = CreateFrame("Frame", name, container)
|
||||
header:SetHeight(20)
|
||||
header:EnableMouse(true)
|
||||
local text = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
text:SetPoint("LEFT", header, "LEFT", 0, 0)
|
||||
header.text = text
|
||||
|
||||
header:SetScript("OnEnter", function()
|
||||
if this.fullName and this.isShortened then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:SetText(this.fullName)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
header:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
end
|
||||
header.inUse = true
|
||||
return header
|
||||
end
|
||||
|
||||
-- Create or return a bag parent frame for a frame prefix and bag parents table
|
||||
function Guda_GetBagParent(framePrefix, parentsTable, bagID, containerName)
|
||||
local container = getglobal(containerName)
|
||||
if not parentsTable[bagID] then
|
||||
local name = framePrefix .. "_BagParent" .. bagID
|
||||
parentsTable[bagID] = CreateFrame("Frame", name, container)
|
||||
parentsTable[bagID]:SetAllPoints(container)
|
||||
if parentsTable[bagID].SetID then
|
||||
parentsTable[bagID]:SetID(bagID)
|
||||
end
|
||||
end
|
||||
return parentsTable[bagID]
|
||||
end
|
||||
|
||||
-- Update lock/desaturation states for a table of parent frames
|
||||
function Guda_UpdateLockStates(parentsTable)
|
||||
if not parentsTable then return end
|
||||
for _, parent in pairs(parentsTable) do
|
||||
if parent then
|
||||
local buttons = { parent:GetChildren() }
|
||||
for _, button in ipairs(buttons) do
|
||||
if button.hasItem ~= nil and button:IsShown() and button.bagID and button.slotID then
|
||||
local ok, name, texture, count, quality, canUse = pcall(function() return GetContainerItemInfo(button.bagID, button.slotID) end)
|
||||
local _, _, locked = nil, nil, nil
|
||||
if ok then
|
||||
-- older GetContainerItemInfo returns texture, count, locked etc in different orders; try to call a safe wrapper if available
|
||||
local infoOk, iName, iTexture, iCount, iQuality, iCanUse, iLocked = pcall(function() return GetContainerItemInfo(button.bagID, button.slotID) end)
|
||||
if infoOk then
|
||||
-- Try to find 'locked' boolean among returned values (best-effort)
|
||||
for _, val in ipairs({iName, iTexture, iCount, iQuality, iCanUse, iLocked}) do
|
||||
if type(val) == "boolean" then locked = val; break end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not button.otherChar and not button.isReadOnly and SetItemButtonDesaturated and locked ~= nil then
|
||||
SetItemButtonDesaturated(button, locked, 0.5, 0.5, 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Shared search filter used by BagFrame and BankFrame
|
||||
function Guda_PassesSearchFilter(itemData, searchText)
|
||||
-- If no search text, everything matches
|
||||
if not searchText or searchText == "" then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Ignore common placeholders
|
||||
if searchText == "Search, try ~equipment" or searchText == "Search bank..." then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Empty slots don't match when searching
|
||||
if not itemData then
|
||||
return false
|
||||
end
|
||||
|
||||
local itemName = itemData.name
|
||||
if not itemName and itemData.link then
|
||||
local _, _, name = string.find(itemData.link, "%[(.+)%]")
|
||||
itemName = name
|
||||
end
|
||||
|
||||
if not itemName then return false end
|
||||
|
||||
local search = string.lower(searchText)
|
||||
|
||||
if string.sub(search, 1, 1) == "~" then
|
||||
local category = string.sub(search, 2)
|
||||
local itemType = itemData.class or ""
|
||||
local itemQuality = itemData.quality or -1
|
||||
|
||||
if category == "equipment" or category == "armor" or category == "weapon" then
|
||||
if itemType == "Armor" or itemType == "Weapon" then return true end
|
||||
elseif category == "consumable" then
|
||||
if itemType == "Consumable" then return true end
|
||||
elseif category == "tradegoods" or category == "trades" then
|
||||
if itemType == "Trade Goods" then return true end
|
||||
elseif category == "quest" then
|
||||
local isQuest, isQuestStarter = Guda_GetQuestInfo(itemData.bagID, itemData.slotID, itemData.isBank)
|
||||
if isQuest or isQuestStarter or itemType == "Quest" then return true end
|
||||
elseif category == "reagent" then
|
||||
if itemType == "Reagent" then return true end
|
||||
elseif category == "common" then if itemQuality == 1 then return true end
|
||||
elseif category == "uncommon" then if itemQuality == 2 then return true end
|
||||
elseif category == "rare" then if itemQuality == 3 then return true end
|
||||
elseif category == "epic" then if itemQuality == 4 then return true end
|
||||
elseif category == "legendary" then if itemQuality == 5 then return true end
|
||||
end
|
||||
end
|
||||
|
||||
itemName = string.lower(itemName)
|
||||
return string.find(itemName, string.lower(searchText), 1, true) ~= nil
|
||||
end
|
||||
|
||||
-- Generic ResizeFrame for Bag/Bank frames
|
||||
function Guda_ResizeFrame(frameName, containerName, currentRow, currentCol, columns, overrideHeight)
|
||||
local buttonSize = addon.Modules.DB:GetSetting("iconSize") or addon.Constants.BUTTON_SIZE
|
||||
local spacing = addon.Modules.DB:GetSetting("iconSpacing") or addon.Constants.BUTTON_SPACING
|
||||
|
||||
local totalRows = (currentRow or 0) + 1
|
||||
if totalRows < 1 then totalRows = 1 end
|
||||
|
||||
local containerWidth = (columns * (buttonSize + spacing)) + 20
|
||||
local containerHeight = overrideHeight or ((totalRows * (buttonSize + spacing)) + 20)
|
||||
local frameWidth = containerWidth + 20
|
||||
|
||||
local showSearchBar = addon.Modules.DB:GetSetting("showSearchBar")
|
||||
if showSearchBar == nil then showSearchBar = true end
|
||||
|
||||
local titleHeight = 40
|
||||
local searchBarHeight = 30
|
||||
local footerHeight = 45
|
||||
local frameHeight
|
||||
|
||||
local hideFooter = addon.Modules.DB:GetSetting("hideFooter")
|
||||
if hideFooter then
|
||||
footerHeight = 10
|
||||
frameHeight = containerHeight + titleHeight + (showSearchBar and searchBarHeight or 0) + footerHeight
|
||||
elseif showSearchBar then
|
||||
frameHeight = containerHeight + titleHeight + searchBarHeight + footerHeight
|
||||
else
|
||||
frameHeight = containerHeight + titleHeight + footerHeight
|
||||
end
|
||||
|
||||
if containerWidth < 200 then
|
||||
containerWidth = 200
|
||||
frameWidth = 220
|
||||
end
|
||||
if containerHeight < 150 then containerHeight = 150 end
|
||||
if frameHeight < 250 then frameHeight = 250 end
|
||||
|
||||
if containerWidth > 1250 then containerWidth = 1250; frameWidth = 1270 end
|
||||
if containerHeight > 1000 then containerHeight = 1000 end
|
||||
if frameHeight > 1200 then frameHeight = 1200 end
|
||||
|
||||
local frame = getglobal(frameName)
|
||||
local itemContainer = getglobal(containerName)
|
||||
|
||||
if frame then
|
||||
frame:SetWidth(frameWidth)
|
||||
frame:SetHeight(frameHeight)
|
||||
frame:ClearAllPoints()
|
||||
-- Try to preserve saved position if present (saved only for Bag frame)
|
||||
if addon and addon.Modules and addon.Modules.DB then
|
||||
local settingName = (frameName == "Guda_BagFrame") and "bagFramePosition" or nil
|
||||
if settingName then
|
||||
local pos = addon.Modules.DB:GetSetting(settingName)
|
||||
if pos and pos.point == "BOTTOMRIGHT" and pos.x and pos.y then
|
||||
frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT", pos.x, pos.y)
|
||||
else
|
||||
frame:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -50, 100)
|
||||
end
|
||||
else
|
||||
frame:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -50, 100)
|
||||
end
|
||||
else
|
||||
frame:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -50, 100)
|
||||
end
|
||||
end
|
||||
|
||||
if itemContainer then
|
||||
itemContainer:SetWidth(containerWidth)
|
||||
itemContainer:SetHeight(containerHeight)
|
||||
end
|
||||
|
||||
-- Resize search bar and toolbar to match container width
|
||||
local searchBar = getglobal(frameName .. "_SearchBar")
|
||||
if searchBar then searchBar:SetWidth(containerWidth) end
|
||||
local toolbar = getglobal(frameName .. "_Toolbar")
|
||||
if toolbar then toolbar:SetWidth(containerWidth) end
|
||||
end
|
||||
+120
-14
@@ -11,6 +11,8 @@ scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
-- Check if an item is a quest item by scanning its tooltip
|
||||
-- Check if an item is a quest item by scanning its tooltip and determine type
|
||||
local function IsQuestItem(bagID, slotID, isBank)
|
||||
bagID = tonumber(bagID)
|
||||
slotID = tonumber(slotID)
|
||||
if not bagID or not slotID then return false end
|
||||
|
||||
scanTooltip:ClearLines()
|
||||
@@ -117,6 +119,8 @@ end
|
||||
|
||||
-- Scan tooltip for red text that is NOT a durability line
|
||||
local function IsItemUnusable(bagID, slotID, isBank)
|
||||
bagID = tonumber(bagID)
|
||||
slotID = tonumber(slotID)
|
||||
if not bagID or not slotID then return false end
|
||||
|
||||
-- Some clients require SetOwner before every SetBagItem/SetInventoryItem to populate lines
|
||||
@@ -377,6 +381,15 @@ function Guda_ItemButton_OnLoad(self)
|
||||
|
||||
-- Default behavior
|
||||
if ContainerFrameItemButton_OnClick then
|
||||
-- Mailbox clicks should be ignored except for Ctrl+Click (preview)
|
||||
-- OR if it's a live mail item for the current player and Shift+Click (loot)
|
||||
if this.isMail then
|
||||
if IsControlKeyDown() then
|
||||
ContainerFrameItemButton_OnClick(arg1)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
ContainerFrameItemButton_OnClick(arg1)
|
||||
end
|
||||
end)
|
||||
@@ -425,6 +438,10 @@ end
|
||||
|
||||
-- Set item data
|
||||
function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly)
|
||||
-- Proactively convert to number to avoid comparisons with strings in downstream functions
|
||||
bagID = tonumber(bagID)
|
||||
slotID = tonumber(slotID)
|
||||
|
||||
-- Proactively clear any previous cooldown overlay state before reassigning this pooled button
|
||||
do
|
||||
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
||||
@@ -447,13 +464,20 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
self.bagID = bagID
|
||||
self.slotID = slotID
|
||||
-- Also set the Blizzard slot ID for compatibility with ContainerFrameItemButtonTemplate behavior
|
||||
if self.SetID and slotID then
|
||||
self:SetID(slotID)
|
||||
-- ALWAYS set ID to something (0 if nil) to avoid leaking old IDs when button is reused
|
||||
if self.SetID then
|
||||
self:SetID(slotID or 0)
|
||||
end
|
||||
-- Explicitly set bag index to avoid Blizzard's ContainerFrameItemButton_OnEnter logic
|
||||
-- from picking up this button as part of a real bag.
|
||||
self.bagIndex = bagID or -100 -- Use an invalid bag index for non-bag buttons
|
||||
self.itemData = itemData
|
||||
self.isBank = isBank or false
|
||||
self.otherChar = otherCharName
|
||||
self.isReadOnly = isReadOnly or false -- Track if this is read-only mode
|
||||
self.isMail = false -- Clear mailbox flag by default
|
||||
self.mailIndex = nil
|
||||
self.mailItemIndex = nil
|
||||
|
||||
-- Re-register for drag/drop every time (crucial for button reuse in Classic/Vanilla)
|
||||
if not self.isReadOnly and not self.otherChar then
|
||||
@@ -481,6 +505,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
matchesFilter = true
|
||||
end
|
||||
|
||||
self.mailData = nil -- Clear mail metadata by default
|
||||
|
||||
-- Use Blizzard's default count fontstring (ContainerFrameItemButtonTemplate creates $parentCount)
|
||||
local countText = getglobal(self:GetName().."Count")
|
||||
local emptySlotBg = getglobal(self:GetName().."_EmptySlotBg")
|
||||
@@ -538,7 +564,17 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
|
||||
-- Apply the determined texture and count
|
||||
if self.hasItem then
|
||||
if SetItemButtonTexture then SetItemButtonTexture(self, displayTexture) end
|
||||
if SetItemButtonTexture then
|
||||
SetItemButtonTexture(self, displayTexture)
|
||||
end
|
||||
|
||||
-- Explicitly set and show icon texture as SetItemButtonTexture can be unreliable for custom paths in 1.12
|
||||
local iconTexture = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
||||
if iconTexture and displayTexture then
|
||||
iconTexture:SetTexture(displayTexture)
|
||||
iconTexture:Show()
|
||||
end
|
||||
|
||||
if SetItemButtonCount then SetItemButtonCount(self, displayCount or 1) end
|
||||
if emptySlotBg then emptySlotBg:Hide() end
|
||||
-- Update cooldown overlay for live items
|
||||
@@ -625,6 +661,9 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
-- For 1.12.1 compatibility, access textures using both methods and proper naming
|
||||
-- We'll set the NormalTexture later based on whether slot is empty or filled
|
||||
|
||||
-- ICON TEXTURE: Ensure we use the correct name ($parentIconTexture is standard)
|
||||
local iconTexture = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
||||
|
||||
-- Pushed texture follows the button size/position
|
||||
local pushedTexture = getglobal(self:GetName().."PushedTexture")
|
||||
if not pushedTexture and self.GetPushedTexture then
|
||||
@@ -744,7 +783,7 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
-- Special border for keyring items (cyan/blue)
|
||||
self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1)
|
||||
self.qualityBorder:Show()
|
||||
elseif itemQuality and itemLink then
|
||||
elseif itemQuality then
|
||||
-- Check settings to determine if we should show borders (nil-safe)
|
||||
local showEquipmentBorder, showOtherBorder
|
||||
if addon and addon.Modules and addon.Modules.DB and addon.Modules.DB.GetSetting then
|
||||
@@ -765,10 +804,12 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
|
||||
-- Check if item is equipment (nil-safe)
|
||||
local isEquipment = false
|
||||
if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsEquipment then
|
||||
isEquipment = addon.Modules.Utils:IsEquipment(itemLink)
|
||||
elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsEquipment then
|
||||
isEquipment = Guda.Modules.Utils:IsEquipment(itemLink)
|
||||
if itemLink then
|
||||
if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsEquipment then
|
||||
isEquipment = addon.Modules.Utils:IsEquipment(itemLink)
|
||||
elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsEquipment then
|
||||
isEquipment = Guda.Modules.Utils:IsEquipment(itemLink)
|
||||
end
|
||||
end
|
||||
|
||||
-- Determine if we should show the border based on item type and settings
|
||||
@@ -920,8 +961,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
self.questIcon:ClearAllPoints()
|
||||
self.questIcon:SetPoint("TOPRIGHT", self, "TOPRIGHT", 1, 0)
|
||||
end
|
||||
else
|
||||
-- Hide icon for empty slots
|
||||
elseif not self.isMail then
|
||||
-- Hide icon for empty slots, but keep it for mailbox custom icons
|
||||
iconTexture:Hide()
|
||||
end
|
||||
end
|
||||
@@ -929,7 +970,7 @@ end
|
||||
|
||||
-- OnEnter handler (show tooltip)
|
||||
function Guda_ItemButton_OnEnter(self)
|
||||
-- Highlight the corresponding bag button in the footer (works for empty and filled slots)
|
||||
-- Highlight the corresponding bag button in the footer (works for empty and filled slots)
|
||||
if not self.otherChar and self.bagID then
|
||||
if self.isBank then
|
||||
-- Bank item - highlight bank bag button
|
||||
@@ -941,7 +982,7 @@ function Guda_ItemButton_OnEnter(self)
|
||||
end
|
||||
|
||||
-- Early return for empty slots (no tooltip needed)
|
||||
if not self.hasItem then
|
||||
if not self.hasItem and not self.isMail then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -949,7 +990,71 @@ function Guda_ItemButton_OnEnter(self)
|
||||
-- important position of tooltip
|
||||
GameTooltip:SetPoint("BOTTOMRIGHT", self, "TOPLEFT", 10, 0)
|
||||
|
||||
if self.otherChar or self.isReadOnly then
|
||||
-- Mailbox tooltip handling
|
||||
if self.isMail then
|
||||
local currentPlayerName = addon.Modules.DB:GetPlayerFullName()
|
||||
local isMailboxOpen = addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen()
|
||||
|
||||
if (not self.otherChar or self.otherChar == currentPlayerName) and self.mailIndex and isMailboxOpen then
|
||||
-- Live mailbox for current character (only when mailbox is actually open)
|
||||
GameTooltip:SetInboxItem(self.mailIndex, self.mailItemIndex or 1)
|
||||
elseif self.itemData and (self.itemData.link or self.itemData.itemID) then
|
||||
-- Read-only / other character mailbox OR current character mailbox when closed
|
||||
GameTooltip.GudaViewedCharacter = self.otherChar or currentPlayerName
|
||||
if self.itemData.link then
|
||||
GameTooltip:SetHyperlink(self.itemData.link)
|
||||
else
|
||||
GameTooltip:SetHyperlink("item:" .. self.itemData.itemID .. ":0:0:0")
|
||||
end
|
||||
elseif self.itemData and self.itemData.name then
|
||||
-- Money or generic mail
|
||||
GameTooltip:AddLine(self.itemData.name, 1, 1, 1)
|
||||
elseif self.mailData and self.mailData.money and self.mailData.money > 0 then
|
||||
-- Fallback for money only mail
|
||||
GameTooltip:AddLine("Money", 1, 1, 1)
|
||||
end
|
||||
|
||||
-- Add mailbox metadata if available
|
||||
local mailData = self.mailData
|
||||
|
||||
if mailData then
|
||||
-- If we already added a line (for money/generic), or SetHyperlink/SetInboxItem added lines,
|
||||
-- we might want a separator if we're adding sender info.
|
||||
if GameTooltip:NumLines() > 0 then
|
||||
GameTooltip:AddLine(" ")
|
||||
end
|
||||
|
||||
if mailData.sender then
|
||||
GameTooltip:AddLine("From: " .. mailData.sender, 1, 1, 1)
|
||||
end
|
||||
if mailData.subject then
|
||||
GameTooltip:AddLine("Subject: " .. mailData.subject, 1, 1, 0.8)
|
||||
end
|
||||
if (mailData.money or 0) > 0 and not (self.itemData and self.itemData.name == "Money") then
|
||||
GameTooltip:AddLine("Money: " .. addon.Modules.Utils:FormatMoney(mailData.money), 1, 1, 1)
|
||||
end
|
||||
if (mailData.CODAmount or 0) > 0 then
|
||||
GameTooltip:AddLine("COD: " .. addon.Modules.Utils:FormatMoney(mailData.CODAmount), 1, 0, 0)
|
||||
end
|
||||
if mailData.daysLeft then
|
||||
GameTooltip:AddLine("Days left: " .. math.floor(mailData.daysLeft), 0.5, 0.5, 0.5)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add Inventory counts for mailbox items at the very bottom
|
||||
if addon.Modules.Tooltip and addon.Modules.Tooltip.AddInventoryInfo then
|
||||
local link = self.itemData and (self.itemData.link or (self.itemData.itemID and ("item:" .. self.itemData.itemID .. ":0:0:0")))
|
||||
if not link and self.mailIndex and isMailboxOpen then
|
||||
link = addon.Modules.Utils:GetInboxItemLink(self.mailIndex, self.mailItemIndex or 1)
|
||||
end
|
||||
if link then
|
||||
addon.Modules.Tooltip:AddInventoryInfo(GameTooltip, link)
|
||||
end
|
||||
end
|
||||
|
||||
GameTooltip:Show()
|
||||
return
|
||||
elseif self.otherChar or self.isReadOnly then
|
||||
GameTooltip.GudaViewedCharacter = self.otherChar
|
||||
if self.itemData and self.itemData.link then
|
||||
GameTooltip:SetHyperlink(self.itemData.link)
|
||||
@@ -1017,8 +1122,9 @@ end
|
||||
-- OnLeave handler
|
||||
function Guda_ItemButton_OnLeave(self)
|
||||
-- Clear any viewed character hint on the tooltip when leaving
|
||||
if GameTooltip and GameTooltip.GudaViewedCharacter then
|
||||
if GameTooltip then
|
||||
GameTooltip.GudaViewedCharacter = nil
|
||||
GameTooltip.GudaInventoryAdded = nil
|
||||
end
|
||||
GameTooltip:Hide()
|
||||
ResetCursor()
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
-- Mailbox Frame
|
||||
-- Mailbox viewing UI
|
||||
|
||||
local addon = Guda
|
||||
|
||||
local MailboxFrame = {}
|
||||
addon.Modules.MailboxFrame = MailboxFrame
|
||||
|
||||
local currentViewChar = nil
|
||||
local searchText = ""
|
||||
local isReadOnlyMode = true -- Mailbox is always read-only in this addon (viewing offline data)
|
||||
local itemButtons = {}
|
||||
local mailboxRows = {}
|
||||
local currentPage = 1
|
||||
local ITEMS_PER_PAGE = 7
|
||||
local mailboxClickCatcher = nil
|
||||
|
||||
-- OnLoad
|
||||
function Guda_MailboxFrame_OnLoad(self)
|
||||
-- Apply border visibility setting
|
||||
if MailboxFrame.UpdateBorderVisibility then
|
||||
MailboxFrame:UpdateBorderVisibility()
|
||||
end
|
||||
|
||||
-- Set up initial backdrop
|
||||
-- (No longer needed here as UpdateBorderVisibility handles it)
|
||||
|
||||
-- Set up search box placeholder
|
||||
local searchBox = getglobal(self:GetName().."_SearchBar_SearchBox")
|
||||
if searchBox then
|
||||
searchBox:SetText("Search mailbox...")
|
||||
searchBox:SetTextColor(0.5, 0.5, 0.5, 1)
|
||||
end
|
||||
|
||||
-- Create invisible full-screen frame to catch clicks outside the mailbox frame while typing in search
|
||||
if not mailboxClickCatcher then
|
||||
mailboxClickCatcher = CreateFrame("Frame", "Guda_MailboxClickCatcher", UIParent)
|
||||
mailboxClickCatcher:SetFrameStrata("BACKGROUND")
|
||||
mailboxClickCatcher:SetAllPoints(UIParent)
|
||||
mailboxClickCatcher:EnableMouse(true)
|
||||
mailboxClickCatcher:Hide()
|
||||
|
||||
mailboxClickCatcher:SetScript("OnMouseDown", function()
|
||||
if Guda_MailboxFrame_ClearSearch then
|
||||
Guda_MailboxFrame_ClearSearch()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear search focus
|
||||
function Guda_MailboxFrame_ClearSearch()
|
||||
local searchBox = getglobal("Guda_MailboxFrame_SearchBar_SearchBox")
|
||||
if searchBox then
|
||||
searchBox:ClearFocus()
|
||||
end
|
||||
end
|
||||
|
||||
-- OnShow
|
||||
function Guda_MailboxFrame_OnShow(self)
|
||||
-- Apply border visibility setting
|
||||
if MailboxFrame.UpdateBorderVisibility then
|
||||
MailboxFrame:UpdateBorderVisibility()
|
||||
end
|
||||
|
||||
-- Apply frame transparency
|
||||
if Guda_ApplyBackgroundTransparency then
|
||||
Guda_ApplyBackgroundTransparency()
|
||||
end
|
||||
|
||||
MailboxFrame:Update()
|
||||
end
|
||||
|
||||
-- Update border visibility
|
||||
function MailboxFrame:UpdateBorderVisibility()
|
||||
local hideBorders = addon.Modules.DB:GetSetting("hideBorders")
|
||||
if hideBorders then
|
||||
Guda:ApplyBackdrop(Guda_MailboxFrame, "MINIMALIST_BORDER", "DEFAULT")
|
||||
else
|
||||
Guda:ApplyBackdrop(Guda_MailboxFrame, "DEFAULT_FRAME", "DEFAULT")
|
||||
end
|
||||
end
|
||||
|
||||
-- OnHide
|
||||
function Guda_MailboxFrame_OnHide(self)
|
||||
-- Close any open dropdown menus when the mailbox frame is hidden
|
||||
CloseDropDownMenus()
|
||||
end
|
||||
|
||||
-- Toggle visibility
|
||||
function MailboxFrame:Toggle()
|
||||
if Guda_MailboxFrame:IsShown() then
|
||||
Guda_MailboxFrame:Hide()
|
||||
else
|
||||
Guda_MailboxFrame:Show()
|
||||
end
|
||||
end
|
||||
|
||||
-- Show specific character's mailbox
|
||||
function MailboxFrame:GetCurrentViewChar()
|
||||
return currentViewChar
|
||||
end
|
||||
|
||||
function MailboxFrame:ShowCharacter(fullName)
|
||||
currentViewChar = fullName
|
||||
currentPage = 1
|
||||
self:Update()
|
||||
end
|
||||
|
||||
-- Pagination
|
||||
function MailboxFrame:NextPage()
|
||||
currentPage = currentPage + 1
|
||||
self:Update()
|
||||
end
|
||||
|
||||
function MailboxFrame:PrevPage()
|
||||
if currentPage > 1 then
|
||||
currentPage = currentPage - 1
|
||||
self:Update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize module
|
||||
function MailboxFrame:Initialize()
|
||||
-- Register events if needed
|
||||
end
|
||||
|
||||
-- Update the mailbox frame
|
||||
function MailboxFrame:Update()
|
||||
if not Guda_MailboxFrame:IsShown() then return end
|
||||
|
||||
-- Determine which character to show
|
||||
local charFullName = currentViewChar or addon.Modules.DB:GetPlayerFullName()
|
||||
local mailboxData = addon.Modules.DB:GetCharacterMailbox(charFullName)
|
||||
|
||||
-- Extract character name from fullName
|
||||
local charName = charFullName
|
||||
local dashPos = string.find(charFullName, "-")
|
||||
if dashPos then
|
||||
charName = string.sub(charFullName, 1, dashPos - 1)
|
||||
end
|
||||
|
||||
getglobal("Guda_MailboxFrame_Title"):SetText(charName .. "'s Mailbox")
|
||||
|
||||
-- Filter items based on search text
|
||||
local filteredItems = {}
|
||||
local totalItems = table.getn(mailboxData)
|
||||
for i, mail in ipairs(mailboxData) do
|
||||
local matchesSearch = true
|
||||
if searchText ~= "" then
|
||||
matchesSearch = false
|
||||
if mail.sender and string.find(string.lower(mail.sender), searchText) then
|
||||
matchesSearch = true
|
||||
elseif mail.subject and string.find(string.lower(mail.subject), searchText) then
|
||||
matchesSearch = true
|
||||
elseif mail.item and mail.item.name and string.find(string.lower(mail.item.name), searchText) then
|
||||
matchesSearch = true
|
||||
end
|
||||
end
|
||||
|
||||
if matchesSearch then
|
||||
table.insert(filteredItems, mail)
|
||||
end
|
||||
end
|
||||
|
||||
-- Display items
|
||||
local emptyMessage = getglobal("Guda_MailboxFrame_EmptyMessage")
|
||||
if emptyMessage then
|
||||
if totalItems == 0 then
|
||||
emptyMessage:SetText("No mailbox data found for this character.\n\nVisit a mailbox in-game to save your mail data.")
|
||||
emptyMessage:Show()
|
||||
else
|
||||
emptyMessage:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
self:DisplayItems(filteredItems, charFullName, totalItems)
|
||||
end
|
||||
|
||||
-- Display mailbox items in a list
|
||||
function MailboxFrame:DisplayItems(items, charFullName, totalMails)
|
||||
local container = getglobal("Guda_MailboxFrame_ItemContainer")
|
||||
|
||||
-- Hide all existing rows first
|
||||
for _, row in pairs(mailboxRows) do
|
||||
row:Hide()
|
||||
end
|
||||
|
||||
local totalItems = table.getn(items)
|
||||
local totalPages = math.max(1, math.ceil(totalItems / ITEMS_PER_PAGE))
|
||||
|
||||
if currentPage > totalPages then
|
||||
currentPage = totalPages
|
||||
end
|
||||
|
||||
local startIndex = (currentPage - 1) * ITEMS_PER_PAGE + 1
|
||||
local endIndex = math.min(startIndex + ITEMS_PER_PAGE - 1, totalItems)
|
||||
|
||||
local rowIndex = 1
|
||||
for i = startIndex, endIndex do
|
||||
local mail = items[i]
|
||||
local row = mailboxRows[rowIndex]
|
||||
if not row then
|
||||
row = CreateFrame("Frame", "Guda_MailboxRow" .. rowIndex, container, "Guda_MailboxRowTemplate")
|
||||
mailboxRows[rowIndex] = row
|
||||
end
|
||||
|
||||
row:ClearAllPoints()
|
||||
row:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -(rowIndex - 1) * 55)
|
||||
|
||||
-- Row Money Frame
|
||||
local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame")
|
||||
if moneyFrame then
|
||||
-- Ensure row money frame doesn't auto-update to character money
|
||||
moneyFrame.moneyType = "STATIC"
|
||||
moneyFrame:UnregisterAllEvents()
|
||||
moneyFrame:SetScript("OnShow", nil) -- Disable Blizzard's auto-update OnShow
|
||||
moneyFrame:SetScript("OnEvent", nil)
|
||||
|
||||
-- Restore right alignment for the money frame to avoid overlap with icon
|
||||
moneyFrame:ClearAllPoints()
|
||||
moneyFrame:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -10, 8)
|
||||
end
|
||||
|
||||
if (mail.money or 0) > 0 then
|
||||
moneyFrame:Show()
|
||||
MoneyFrame_Update(moneyFrame:GetName(), mail.money)
|
||||
else
|
||||
if moneyFrame then moneyFrame:Hide() end
|
||||
end
|
||||
|
||||
-- Fill row data
|
||||
local sender = mail.sender or "Unknown"
|
||||
getglobal(row:GetName() .. "_Sender"):SetText(sender)
|
||||
|
||||
local subject = mail.subject or "No Subject"
|
||||
if mail.item and mail.item.quality then
|
||||
local r, g, b = addon.Modules.Utils:GetQualityColor(mail.item.quality)
|
||||
subject = addon.Modules.Utils:ColorText(subject, r, g, b)
|
||||
end
|
||||
getglobal(row:GetName() .. "_Subject"):SetText(subject)
|
||||
|
||||
local expireText = ""
|
||||
if mail.daysLeft then
|
||||
if mail.daysLeft < 1 then
|
||||
expireText = string.format("|cffff0000%dh|r", math.floor(mail.daysLeft * 24))
|
||||
else
|
||||
expireText = string.format("%dd", math.floor(mail.daysLeft))
|
||||
end
|
||||
end
|
||||
getglobal(row:GetName() .. "_ExpireTime"):SetText(expireText)
|
||||
|
||||
-- Item Button
|
||||
local itemButton = getglobal(row:GetName() .. "_ItemButton")
|
||||
-- Re-assign shared namespace function if needed, but it should be available globally or via addon
|
||||
|
||||
itemButton.isBank = false
|
||||
itemButton.otherChar = charFullName
|
||||
itemButton.mailData = mail
|
||||
itemButton.isMail = true
|
||||
itemButton.mailIndex = mail.mailIndex
|
||||
itemButton.mailItemIndex = mail.itemIndex or 1
|
||||
|
||||
-- Center vertically in the row
|
||||
if itemButton then
|
||||
itemButton:ClearAllPoints()
|
||||
itemButton:SetPoint("LEFT", row, "LEFT", 10, 0)
|
||||
end
|
||||
|
||||
if mail.item and (mail.item.texture or mail.item.link) then
|
||||
Guda_ItemButton_SetItem(itemButton, nil, nil, mail.item, false, charFullName, true, true)
|
||||
itemButton.isMail = true
|
||||
itemButton.mailData = mail
|
||||
itemButton.mailIndex = mail.mailIndex
|
||||
itemButton.mailItemIndex = mail.itemIndex or 1
|
||||
else
|
||||
Guda_ItemButton_SetItem(itemButton, nil, nil, nil, false, charFullName, true, true)
|
||||
itemButton.isMail = true
|
||||
itemButton.mailData = mail
|
||||
itemButton.mailIndex = mail.mailIndex
|
||||
itemButton.mailItemIndex = 1
|
||||
|
||||
local icon = getglobal(itemButton:GetName().."IconTexture") or getglobal(itemButton:GetName().."Icon")
|
||||
if icon then
|
||||
if (mail.money or 0) > 0 then
|
||||
icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01")
|
||||
elseif mail.packageIcon then
|
||||
icon:SetTexture(mail.packageIcon)
|
||||
else
|
||||
icon:SetTexture("Interface\\Icons\\INV_Letter_15")
|
||||
end
|
||||
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
icon:Show()
|
||||
end
|
||||
if itemButton.qualityBorder then itemButton.qualityBorder:Hide() end
|
||||
end
|
||||
|
||||
row:Show()
|
||||
rowIndex = rowIndex + 1
|
||||
end
|
||||
|
||||
-- Update pagination buttons
|
||||
local prevBtn = getglobal("Guda_MailboxFrame_PrevPageButton")
|
||||
local nextBtn = getglobal("Guda_MailboxFrame_NextPageButton")
|
||||
|
||||
if currentPage > 1 then
|
||||
prevBtn:Enable()
|
||||
else
|
||||
prevBtn:Disable()
|
||||
end
|
||||
|
||||
if currentPage < totalPages then
|
||||
nextBtn:Enable()
|
||||
else
|
||||
nextBtn:Disable()
|
||||
end
|
||||
|
||||
-- Update pagination text
|
||||
local paginationText = getglobal("Guda_MailboxFrame_Pagination_Text")
|
||||
if paginationText then
|
||||
if totalMails == 0 then
|
||||
paginationText:SetText("No Mail")
|
||||
else
|
||||
paginationText:SetText(string.format("%d/%d (items: %d)", currentPage, totalPages, totalMails or 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Search text changed
|
||||
function Guda_MailboxFrame_OnSearchTextChanged()
|
||||
local searchBox = getglobal("Guda_MailboxFrame_SearchBar_SearchBox")
|
||||
local text = searchBox:GetText()
|
||||
if text == "Search mailbox..." then
|
||||
searchText = ""
|
||||
else
|
||||
searchText = string.lower(text)
|
||||
end
|
||||
currentPage = 1
|
||||
MailboxFrame:Update()
|
||||
end
|
||||
|
||||
-- Show character selection menu
|
||||
local function Guda_MailboxCharacterMenu_Initialize()
|
||||
local characters = addon.Modules.DB:GetAllCharacters(true, true)
|
||||
local info
|
||||
|
||||
for i, char in ipairs(characters) do
|
||||
local charFullName = char.fullName
|
||||
local charClassToken = char.classToken
|
||||
|
||||
-- Get class color
|
||||
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
|
||||
local r, g, b = 1, 1, 1
|
||||
if classColor then
|
||||
r, g, b = classColor.r, classColor.g, classColor.b
|
||||
end
|
||||
|
||||
-- Create colored name
|
||||
local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b)
|
||||
|
||||
info = {}
|
||||
info.text = coloredName
|
||||
info.func = function() MailboxFrame:ShowCharacter(charFullName) end
|
||||
info.checked = (currentViewChar == char.fullName or (not currentViewChar and char.fullName == addon.Modules.DB:GetPlayerFullName()))
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end
|
||||
end
|
||||
|
||||
function Guda_MailboxFrame_ShowCharacterMenu()
|
||||
local menuFrame = getglobal("Guda_MailboxCharacterMenu")
|
||||
if not menuFrame then
|
||||
menuFrame = CreateFrame("Frame", "Guda_MailboxCharacterMenu", UIParent, "UIDropDownMenuTemplate")
|
||||
end
|
||||
UIDropDownMenu_Initialize(menuFrame, Guda_MailboxCharacterMenu_Initialize, "MENU")
|
||||
ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0)
|
||||
end
|
||||
@@ -0,0 +1,355 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/">
|
||||
|
||||
<!-- Mailbox Row Template -->
|
||||
<Frame name="Guda_MailboxRowTemplate" virtual="true">
|
||||
<Size>
|
||||
<AbsDimension x="380" y="50"/>
|
||||
</Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parent_Background">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
<Color r="1" g="1" b="1" a="0.05"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parent_Sender" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Size>
|
||||
<AbsDimension x="140" y="16"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="60" y="-8"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parent_Subject" inherits="GameFontHighlightSmall" justifyH="LEFT">
|
||||
<Size>
|
||||
<AbsDimension x="280" y="14"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parent_Sender" relativePoint="BOTTOMLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="-2"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parent_ExpireTime" inherits="GameFontDisableSmall" justifyH="RIGHT">
|
||||
<Size>
|
||||
<AbsDimension x="100" y="12"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="-10" y="-8"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="$parent_ItemButton" inherits="Guda_ItemButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parent" relativePoint="LEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="10" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Frame name="$parent_MoneyFrame" inherits="SmallMoneyFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="-10" y="8"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- Mailbox Frame -->
|
||||
<Frame name="Guda_MailboxFrame" toplevel="true" movable="true" enableMouse="true" hidden="true" parent="UIParent">
|
||||
<Size>
|
||||
<AbsDimension x="420" y="540"/>
|
||||
</Size>
|
||||
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="CENTER" relativeTo="UIParent">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
|
||||
<Backdrop bgFile="Interface\ChatFrame\ChatFrameBackground" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
||||
<BackgroundInsets>
|
||||
<AbsInset left="11" right="12" top="12" bottom="11"/>
|
||||
</BackgroundInsets>
|
||||
<TileSize>
|
||||
<AbsValue val="16"/>
|
||||
</TileSize>
|
||||
<EdgeSize>
|
||||
<AbsValue val="32"/>
|
||||
</EdgeSize>
|
||||
</Backdrop>
|
||||
|
||||
<Layers>
|
||||
<!-- Title at top -->
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parent_Title" inherits="GameFontNormalLarge">
|
||||
<Anchors>
|
||||
<Anchor point="TOP">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="-12"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
|
||||
<Frames>
|
||||
<!-- Close Button -->
|
||||
<Button name="$parent_CloseButton" inherits="UIPanelCloseButton">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="-16" y="-10"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
Guda_MailboxFrame:Hide()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<Frame name="$parent_SearchBar">
|
||||
<Size>
|
||||
<AbsDimension x="350" y="30"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="20" y="-40"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<EditBox name="$parent_SearchBox" autoFocus="false" inherits="InputBoxTemplate">
|
||||
<Size>
|
||||
<AbsDimension x="0" y="20"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="16" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="-10" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnTextChanged>
|
||||
Guda_MailboxFrame_OnSearchTextChanged()
|
||||
</OnTextChanged>
|
||||
<OnEscapePressed>
|
||||
this:ClearFocus()
|
||||
</OnEscapePressed>
|
||||
<OnEnterPressed>
|
||||
this:ClearFocus()
|
||||
</OnEnterPressed>
|
||||
<OnEditFocusGained>
|
||||
if this:GetText() == "Search mailbox..." then
|
||||
this:SetText("")
|
||||
this:SetTextColor(1, 1, 1, 1)
|
||||
end
|
||||
if Guda_MailboxClickCatcher then
|
||||
Guda_MailboxClickCatcher:Show()
|
||||
end
|
||||
</OnEditFocusGained>
|
||||
<OnEditFocusLost>
|
||||
if this:GetText() == "" then
|
||||
this:SetText("Search mailbox...")
|
||||
this:SetTextColor(0.5, 0.5, 0.5, 1)
|
||||
end
|
||||
if Guda_MailboxClickCatcher then
|
||||
Guda_MailboxClickCatcher:Hide()
|
||||
end
|
||||
</OnEditFocusLost>
|
||||
</Scripts>
|
||||
</EditBox>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- Character Selection Button -->
|
||||
<Button name="$parent_CharacterButton">
|
||||
<Size>
|
||||
<AbsDimension x="24" y="24"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="-20" y="-43"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parent_Icon">
|
||||
<Size>
|
||||
<AbsDimension x="20" y="20"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
<Color r="0.8" g="0.8" b="0.8"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
local icon = getglobal(this:GetName().."_Icon")
|
||||
if icon then
|
||||
icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\Envelope")
|
||||
end
|
||||
</OnLoad>
|
||||
<OnClick>
|
||||
Guda_MailboxFrame_ShowCharacterMenu()
|
||||
</OnClick>
|
||||
<OnEnter>
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:SetText("Switch Character")
|
||||
GameTooltip:Show()
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
GameTooltip:Hide()
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Item Container -->
|
||||
<Frame name="Guda_MailboxFrame_ItemContainer">
|
||||
<Size>
|
||||
<AbsDimension x="360" y="420"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="20" y="-75"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="Guda_MailboxFrame_EmptyMessage" inherits="GameFontNormal" justifyH="CENTER" hidden="true">
|
||||
<Size>
|
||||
<AbsDimension x="300" y="0"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Color r="1" g="0.82" b="0"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
<!-- Pagination -->
|
||||
<Button name="$parent_PrevPageButton" inherits="UIPanelButtonTemplate" text="Prev">
|
||||
<Size>
|
||||
<AbsDimension x="60" y="22"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM">
|
||||
<Offset>
|
||||
<AbsDimension x="-100" y="30"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
Guda.Modules.MailboxFrame:PrevPage()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Button name="$parent_NextPageButton" inherits="UIPanelButtonTemplate" text="Next">
|
||||
<Size>
|
||||
<AbsDimension x="60" y="22"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM">
|
||||
<Offset>
|
||||
<AbsDimension x="100" y="30"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
Guda.Modules.MailboxFrame:NextPage()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame name="$parent_Pagination">
|
||||
<Size>
|
||||
<AbsDimension x="130" y="22"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="30"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parent_Text" inherits="GameFontHighlightSmall" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
</Frames>
|
||||
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Guda_MailboxFrame_OnLoad(this)
|
||||
</OnLoad>
|
||||
<OnShow>
|
||||
Guda_MailboxFrame_OnShow(this)
|
||||
</OnShow>
|
||||
<OnHide>
|
||||
Guda_MailboxFrame_OnHide(this)
|
||||
</OnHide>
|
||||
<OnMouseDown>
|
||||
this:StartMoving()
|
||||
</OnMouseDown>
|
||||
<OnMouseUp>
|
||||
this:StopMovingOrSizing()
|
||||
</OnMouseUp>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
+10
-1
@@ -368,7 +368,7 @@ function Guda_ApplyBackgroundTransparency()
|
||||
local transparency = Guda.Modules.DB:GetSetting("bgTransparency") or 0.15
|
||||
local alpha = 1.0 - transparency
|
||||
|
||||
local frames = { "Guda_BagFrame", "Guda_BankFrame", "Guda_SettingsPopup", "Guda_QuestItemBar" }
|
||||
local frames = { "Guda_BagFrame", "Guda_BankFrame", "Guda_MailboxFrame", "Guda_SettingsPopup", "Guda_QuestItemBar" }
|
||||
for _, frameName in ipairs(frames) do
|
||||
local frame = getglobal(frameName)
|
||||
if frame then
|
||||
@@ -603,6 +603,15 @@ function Guda_SettingsPopup_HideBordersCheckbox_OnClick(self)
|
||||
end
|
||||
end
|
||||
|
||||
local mailboxFrame = getglobal("Guda_MailboxFrame")
|
||||
if mailboxFrame then
|
||||
if isChecked then
|
||||
Guda:ApplyBackdrop(mailboxFrame, "MINIMALIST_BORDER", "DEFAULT")
|
||||
else
|
||||
Guda:ApplyBackdrop(mailboxFrame, "DEFAULT_FRAME", "DEFAULT")
|
||||
end
|
||||
end
|
||||
|
||||
-- Update border visibility on settings frame
|
||||
if SettingsPopup.UpdateBorderVisibility then
|
||||
SettingsPopup:UpdateBorderVisibility()
|
||||
|
||||
Reference in New Issue
Block a user