From df83f8140f0eb36aeffa12548b147ec415a6958d Mon Sep 17 00:00:00 2001 From: Sica Date: Sat, 15 Mar 2025 02:24:37 +0100 Subject: [PATCH] Initial commit as TurtleMail --- .editorconfig | 7 + .luarc.json | 14 + Mail.lua | 793 ----------------------------- Mail.toc | 7 - README.md | 13 +- TurtleMail.lua | 998 +++++++++++++++++++++++++++++++++++++ TurtleMail.toc | 10 + Mail.xml => TurtleMail.xml | 44 +- 8 files changed, 1073 insertions(+), 813 deletions(-) create mode 100644 .editorconfig create mode 100644 .luarc.json delete mode 100644 Mail.lua delete mode 100644 Mail.toc create mode 100644 TurtleMail.lua create mode 100644 TurtleMail.toc rename Mail.xml => TurtleMail.xml (86%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e6783f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.lua] +max_line_length = 160 +space_inside_function_call_parentheses = true +space_inside_function_param_list_parentheses = true +space_inside_square_brackets = true diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..090892d --- /dev/null +++ b/.luarc.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "Lua.diagnostics.globals": [ + "arg1", + "_", + "this", + "event" + ], + "Lua.workspace.checkThirdParty": false, + "Lua.workspace.ignoreDir": [], + "workspace.library": [], + "diagnostics.disable": [], + "Lua.hint.enable": true +} diff --git a/Mail.lua b/Mail.lua deleted file mode 100644 index 562a3ab..0000000 --- a/Mail.lua +++ /dev/null @@ -1,793 +0,0 @@ -Mail = CreateFrame('Frame', nil, MailFrame) -local _G = getfenv(0) -setfenv(1, setmetatable(Mail, {__index=_G})) - -do - local f = CreateFrame'Frame' - f:SetScript('OnEvent', function() Mail[event]() end) - for _, event in {'ADDON_LOADED', 'PLAYER_LOGIN', 'UI_ERROR_MESSAGE', 'CURSOR_UPDATE', 'BAG_UPDATE', 'MAIL_CLOSED', 'MAIL_SEND_SUCCESS', 'MAIL_INBOX_UPDATE'} do - f:RegisterEvent(event) - end -end - -_G.Mail_AutoCompleteNames = {} - -local ATTACHMENTS_MAX = 21 -local ATTACHMENTS_PER_ROW_SEND = 7 -local ATTACHMENTS_MAX_ROWS_SEND = 3 - -function pack(...) return arg end - -do - local f = CreateFrame'Frame' - local cursorItem - function CURSOR_UPDATE() - cursorItem = nil - end - function GetCursorItem() - return cursorItem - end - function SetCursorItem(item) - f:SetScript('OnUpdate', function() - cursorItem = item - f:SetScript('OnUpdate', nil) - end) - end -end - -function BAG_UPDATE() - if MailFrame:IsVisible() then - SendMailFrame_Update() - end -end - -function MAIL_CLOSED() - Inbox_Abort() - SendMail_sending = false - SendMail_Clear() -end - -do - local f = CreateFrame'Frame' - local function update() - f:SetScript('OnUpdate', nil) - SendMail_Send() - end - function MAIL_SEND_SUCCESS() - if SendMail_state then - addAutoCompleteName(SendMail_state.to) - end - if SendMail_sending then - f:SetScript('OnUpdate', update) - end - end -end - -function UI_ERROR_MESSAGE() - if Inbox_opening then - if arg1 == ERR_INV_FULL then - Inbox_Abort() - elseif arg1 == ERR_ITEM_MAX_COUNT then - Inbox_skip = true - end - elseif SendMail_sending and (arg1 == ERR_MAIL_TO_SELF or arg1 == ERR_PLAYER_WRONG_FACTION or arg1 == ERR_MAIL_TARGET_NOT_FOUND or arg1 == ERR_MAIL_REACHED_CAP) then - SendMail_sending = false - SendMail_state = nil - ClearCursor() - orig.ClickSendMailItemButton() - ClearCursor() - end -end - -orig = {} -do - local hooks = {} - hook = setmetatable({}, {__newindex=function(_, k, v) hooks[k] = v end}) - function PLAYER_LOGIN() - for k, v in hooks do - orig[k] = _G[k] - _G[k] = v - end - local key = GetCVar'realmName' .. '|' .. UnitFactionGroup'player' - Mail_AutoCompleteNames[key] = Mail_AutoCompleteNames[key] or {} - for char, lastSeen in Mail_AutoCompleteNames[key] do - if GetTime() - lastSeen > 60 * 60 * 24 * 30 then - Mail_AutoCompleteNames[key][char] = nil - end - end - function addAutoCompleteName(name) - Mail_AutoCompleteNames[key][name] = GetTime() - end - addAutoCompleteName(UnitName'player') - end -end - -function hook.GetInboxHeaderInfo(...) - local sender, canReply = arg[3], arg[12] - if sender and canReply then - addAutoCompleteName(sender) - end - return orig.GetInboxHeaderInfo(unpack(arg)) -end - -function ADDON_LOADED() - if arg1 ~= 'Mail' then return end - - UIPanelWindows.MailFrame.pushable = 1 - UIPanelWindows.FriendsFrame.pushable = 2 - - Inbox_Load() - SendMail_Load() -end - -function hook.OpenMail_Reply(...) - _G.Mail_To = nil - return orig.OpenMail_Reply(unpack(arg)) -end - -function hook.InboxFrame_Update() - orig.InboxFrame_Update() - for i = 1, 7 do - -- hack for tooltip update - _G['MailItem' .. i]:Hide() - _G['MailItem' .. i]:Show() - end - Inbox_UpdateLock() -end - -function hook.InboxFrame_OnClick(i) - if Inbox_opening or arg1 == 'RightButton' and ({GetInboxHeaderInfo(i)})[6] > 0 then - this:SetChecked(nil) - elseif arg1 == 'RightButton' then - Inbox_Open(i) - else - return orig.InboxFrame_OnClick(i) - end -end - -function hook.InboxFrameItem_OnEnter() - orig.InboxFrameItem_OnEnter() - if GetInboxItem(this.index) then - GameTooltip:AddLine(ITEM_OPENABLE, '', 0, 1, 0) - GameTooltip:Show() - end -end - -do - local x = 0 - CreateFrame('Frame', InboxFrame):SetScript('OnUpdate', function() - if x > 0 then - x = x - 1 - elseif not Inbox_opening then - x = 100 - CheckInbox() - end - end) -end - -function Inbox_Load() - local btn = CreateFrame('Button', nil, InboxFrame, 'UIPanelButtonTemplate') - btn:SetPoint('BOTTOM', -10, 90) - btn:SetText(OPENMAIL) - btn:SetWidth(max(120, 30 + ({btn:GetRegions()})[1]:GetStringWidth())) - btn:SetHeight(25) - btn:SetScript('OnClick', Inbox_OpenAll) -end - -do - local i, update - local f = CreateFrame'Frame' - f:Hide() - f:SetScript('OnUpdate', function() - if update then - update = false - local _, _, _, _, _, COD, _, _, _, _, _, _, isGM = GetInboxHeaderInfo(i) - if i > GetInboxNumItems() then - Inbox_Abort() - elseif Inbox_Skip or COD > 0 or isGM then - Inbox_Skip = false - i = i + 1 - update = true - else - Inbox_Open(i) - end - end - end) - function MAIL_INBOX_UPDATE() - if Inbox_opening then - update = true - end - end - function Inbox_OpenAll() - Inbox_opening = true - Inbox_UpdateLock() - i = 1 - Inbox_skip = false - update = true - f:Show() - end - function Inbox_Abort() - Inbox_opening = false - Inbox_UpdateLock() - f:Hide() - end -end - -do - local TakeInboxMoney, TakeInboxItem, DeleteInboxItem = TakeInboxMoney, TakeInboxItem, DeleteInboxItem -- hack to prevent beancounter from deleting mail - function Inbox_Open(i) - GetInboxText(i) - TakeInboxMoney(i) - TakeInboxItem(i) - DeleteInboxItem(i) - end -end - -function Inbox_UpdateLock() - for i = 1, 7 do - _G['MailItem' .. i .. 'ButtonIcon']:SetDesaturated(Inbox_opening) - if Inbox_opening then - _G['MailItem' .. i .. 'Button']:SetChecked(nil) - end - end -end - -function hook.SendMailFrame_Update() - local itemCount = 0 - local itemTitle - local gap - -- local last = 0 blizzlike - local last = SendMail_NumAttachments() - - for i = 1, ATTACHMENTS_MAX do - local btn = _G['MailAttachment' .. i] - - local texture, count - if btn.item then - texture, count = GetContainerItemInfo(unpack(btn.item)) - end - if not texture then - btn:SetNormalTexture(nil) - _G[btn:GetName() .. 'Count']:Hide() - btn.item = nil - else - btn:SetNormalTexture(texture) - if count > 1 then - _G[btn:GetName() .. 'Count']:Show() - _G[btn:GetName() .. 'Count']:SetText(count) - else - _G[btn:GetName() .. 'Count']:Hide() - end - end - end - - if SendMail_NumAttachments() > 0 then - SendMailCODButton:Enable() - SendMailCODButtonText:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) - else - SendMailSendMoneyButton:SetChecked(1) - SendMailCODButton:SetChecked(nil) - SendMailMoneyText:SetText(AMOUNT_TO_SEND) - SendMailCODButton:Disable() - SendMailCODButtonText:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) - end - - MoneyFrame_Update('SendMailCostMoneyFrame', GetSendMailPrice() * max(1, SendMail_NumAttachments())) - - -- Determine how many rows of attachments to show - local itemRowCount = 1 - local temp = last - while temp > ATTACHMENTS_PER_ROW_SEND and itemRowCount < ATTACHMENTS_MAX_ROWS_SEND do - itemRowCount = itemRowCount + 1 - temp = temp - ATTACHMENTS_PER_ROW_SEND - end - - if not gap and temp == ATTACHMENTS_PER_ROW_SEND and itemRowCount < ATTACHMENTS_MAX_ROWS_SEND then - itemRowCount = itemRowCount + 1 - end - if SendMailFrame.maxRowsShown and last > 0 and itemRowCount < SendMailFrame.maxRowsShown then - itemRowCount = SendMailFrame.maxRowsShown - else - SendMailFrame.maxRowsShown = itemRowCount - end - - -- Compute sizes - local cursorx = 0 - local cursory = itemRowCount - 1 - local marginxl = 8 + 6 - local marginxr = 40 + 6 - local areax = SendMailFrame:GetWidth() - marginxl - marginxr - local iconx = MailAttachment1:GetWidth() + 2 - local icony = MailAttachment1:GetHeight() + 2 - local gapx1 = floor((areax - (iconx * ATTACHMENTS_PER_ROW_SEND)) / (ATTACHMENTS_PER_ROW_SEND - 1)) - local gapx2 = floor((areax - (iconx * ATTACHMENTS_PER_ROW_SEND) - (gapx1 * (ATTACHMENTS_PER_ROW_SEND - 1))) / 2) - local gapy1 = 5 - local gapy2 = 6 - local areay = (gapy2 * 2) + (gapy1 * (itemRowCount - 1)) + (icony * itemRowCount) - local indentx = marginxl + gapx2 + 17 - local indenty = 170 + gapy2 + icony - 13 - local tabx = (iconx + gapx1) - 3 --this magic number changes the attachment spacing - local taby = (icony + gapy1) - local scrollHeight = 249 - areay - - MailHorizontalBarLeft:SetPoint('TOPLEFT', SendMailFrame, 'BOTTOMLEFT', 2 + 15, 184 + areay - 14) - - SendMailScrollFrame:SetHeight(scrollHeight) - SendMailScrollChildFrame:SetHeight(scrollHeight) - - local SendMailScrollFrameTop = ({SendMailScrollFrame:GetRegions()})[3] - SendMailScrollFrameTop:SetHeight(scrollHeight) - SendMailScrollFrameTop:SetTexCoord(0, .484375, 0, scrollHeight / 256) - - StationeryBackgroundLeft:SetHeight(scrollHeight) - StationeryBackgroundLeft:SetTexCoord(0, 1, 0, scrollHeight / 256) - - - StationeryBackgroundRight:SetHeight(scrollHeight) - StationeryBackgroundRight:SetTexCoord(0, 1, 0, scrollHeight / 256) - - -- Set Items - for i = 1, ATTACHMENTS_MAX do - if cursory >= 0 then - _G['MailAttachment' .. i]:Enable() - _G['MailAttachment' .. i]:Show() - _G['MailAttachment' .. i]:SetPoint('TOPLEFT', 'SendMailFrame', 'BOTTOMLEFT', indentx + (tabx * cursorx), indenty + (taby * cursory)) - - cursorx = cursorx + 1 - if cursorx >= ATTACHMENTS_PER_ROW_SEND then - cursory = cursory - 1 - cursorx = 0 - end - else - _G['MailAttachment' .. i]:Hide() - end - end - - SendMailFrame_CanSend() -end - -function hook.ClickSendMailItemButton() - SendMail_SetAttachment(GetCursorItem()) -end - -function hook.GetContainerItemInfo(bag, slot) - local ret = pack(orig.GetContainerItemInfo(bag, slot)) - ret[3] = ret[3] or SendMail_Attached(bag, slot) and 1 or nil - return unpack(ret) -end - -function hook.PickupContainerItem(bag, slot) - if SendMail_Attached(bag, slot) then return end - if GetContainerItemInfo(bag, slot) then SetCursorItem{bag, slot} end - return orig.PickupContainerItem(bag, slot) -end - -function hook.SplitContainerItem(bag, slot, amount) - if SendMail_Attached(bag, slot) then return end - return orig.SplitContainerItem(bag, slot, amount) -end - -function hook.UseContainerItem(bag, slot, onself) - if SendMail_Attached(bag, slot) then return end - if IsShiftKeyDown() or IsControlKeyDown() or IsAltKeyDown() then - return orig.UseContainerItem(bag, slot, onself) - elseif MailFrame:IsVisible() then - MailFrameTab_OnClick(2) - SendMail_SetAttachment{bag, slot} - elseif TradeFrame:IsVisible() then - for i = 1, 6 do - if not GetTradePlayerItemLink(i) then - orig.PickupContainerItem(bag, slot) - ClickTradeButton(i) - return - end - end - else - return orig.UseContainerItem(bag, slot, onself) - end -end - -function MailMailButton_OnClick() - MailAutoCompleteBox:Hide() - - _G.Mail_To = SendMailNameEditBox:GetText() - SendMailNameEditBox:HighlightText() - - SendMail_state = { - to = Mail_To, - subject = MailSubjectEditBox:GetText(), - body = SendMailBodyEditBox:GetText(), - money = MoneyInputFrame_GetCopper(SendMailMoney), - cod = SendMailCODButton:GetChecked(), - attachments = SendMail_Attachments(), - numMessages = max(1, SendMail_NumAttachments()), - } - - SendMail_Clear() - SendMail_sending = true - SendMail_Send() -end - -function SendMail_Load() - SendMailFrame:CreateTexture('MailHorizontalBarLeft', 'BACKGROUND') - MailHorizontalBarLeft:SetTexture([[Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar]]) - MailHorizontalBarLeft:SetWidth(256) - MailHorizontalBarLeft:SetHeight(16) - MailHorizontalBarLeft:SetTexCoord(0, 1, 0, .25) - SendMailFrame:CreateTexture('MailHorizontalBarRight', 'BACKGROUND') - MailHorizontalBarRight:SetTexture([[Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar]]) - MailHorizontalBarRight:SetWidth(75) - MailHorizontalBarRight:SetHeight(16) - MailHorizontalBarRight:SetTexCoord(0, .29296875, .25, .5) - MailHorizontalBarRight:SetPoint('LEFT', MailHorizontalBarLeft, 'RIGHT') - - do - local background = ({SendMailPackageButton:GetRegions()})[1] - background:Hide() - local count = ({SendMailPackageButton:GetRegions()})[3] - count:Hide() - SendMailPackageButton:Disable() - SendMailPackageButton:SetScript('OnReceiveDrag', nil) - SendMailPackageButton:SetScript('OnDragStart', nil) - end - - SendMailMoneyText:SetPoint('TOPLEFT', 0, 0) - SendMailMoney:ClearAllPoints() - SendMailMoney:SetPoint('TOPLEFT', SendMailMoneyText, 'BOTTOMLEFT', 5, -5) - SendMailMoneyGoldRight:SetPoint('RIGHT', 20, 0) - do ({SendMailMoneyGold:GetRegions()})[9]:SetDrawLayer('BORDER') end - SendMailMoneyGold:SetMaxLetters(7) - SendMailMoneyGold:SetWidth(50) - SendMailMoneySilverRight:SetPoint('RIGHT', 10, 0) - do ({SendMailMoneySilver:GetRegions()})[9]:SetDrawLayer('BORDER') end - SendMailMoneySilver:SetWidth(28) - SendMailMoneySilver:SetPoint('LEFT', SendMailMoneyGold, 'RIGHT', 30, 0) - SendMailMoneyCopperRight:SetPoint('RIGHT', 10, 0) - do ({SendMailMoneyCopper:GetRegions()})[9]:SetDrawLayer('BORDER') end - SendMailMoneyCopper:SetWidth(28) - SendMailMoneyCopper:SetPoint('LEFT', SendMailMoneySilver, 'RIGHT', 20, 0) - SendMailSendMoneyButton:SetPoint('TOPLEFT', SendMailMoney, 'TOPRIGHT', 0, 12) - - -- hack to avoid automatic subject setting and button disabling from weird blizzard code - MailMailButton = SendMailMailButton - _G.SendMailMailButton = setmetatable({}, {__index = function() return function() end end}) - _G.SendMailMailButton_OnClick = MailMailButton_OnClick - MailSubjectEditBox = SendMailSubjectEditBox - _G.SendMailSubjectEditBox = setmetatable({}, { - __index = function(_, key) - return function(_, ...) - return MailSubjectEditBox[key](MailSubjectEditBox, unpack(arg)) - end - end, - }) - - SendMailNameEditBox._SetText = SendMailNameEditBox.SetText - function SendMailNameEditBox:SetText(...) - if not Mail_To then - return self:_SetText(unpack(arg)) - end - end - SendMailNameEditBox:SetScript('OnShow', function() - if Mail_To then - this:_SetText(Mail_To) - end - end) - SendMailNameEditBox:SetScript('OnChar', function() - _G.Mail_To = nil - GetSuggestions() - end) - SendMailNameEditBox:SetScript('OnTabPressed', function() - if MailAutoCompleteBox:IsVisible() then - if IsShiftKeyDown() then - PreviousMatch() - else - NextMatch() - end - else - MailSubjectEditBox:SetFocus() - end - end) - SendMailNameEditBox:SetScript('OnEnterPressed', function() - if MailAutoCompleteBox:IsVisible() then - MailAutoCompleteBox:Hide() - this:HighlightText(0, 0) - else - MailSubjectEditBox:SetFocus() - end - end) - SendMailNameEditBox:SetScript('OnEscapePressed', function() - if MailAutoCompleteBox:IsVisible() then - MailAutoCompleteBox:Hide() - else - this:ClearFocus() - end - end) - function SendMailNameEditBox.focusLoss() - MailAutoCompleteBox:Hide() - end - do - local orig = SendMailNameEditBox:GetScript'OnTextChanged' - SendMailNameEditBox:SetScript('OnTextChanged', function() - local text = this:GetText() - local formatted = gsub(strlower(text), '^%l', strupper) - if text ~= formatted then - this:SetText(formatted) - end - return orig() - end) - end - - for _, editBox in {SendMailNameEditBox, SendMailSubjectEditBox} do - editBox:SetScript('OnEditFocusGained', function() - this:HighlightText() - end) - editBox:SetScript('OnEditFocusLost', function() - (this.focusLoss or function() end)() - this:HighlightText(0, 0) - end) - do - local lastClick - editBox:SetScript('OnMouseDown', function() - local x, y = GetCursorPosition() - if lastClick and GetTime() - lastClick.t < .5 and x == lastClick.x and y == lastClick.y then - this:SetScript('OnUpdate', function() - this:HighlightText() - this:SetScript('OnUpdate', nil) - end) - end - lastClick = {t=GetTime(), x=x, y=y} - end) - end - end -end - -function hook.SendMailFrame_CanSend() - if not SendMail_sending and strlen(SendMailNameEditBox:GetText()) > 0 and (SendMailSendMoneyButton:GetChecked() and MoneyInputFrame_GetCopper(SendMailMoney) or 0) + GetSendMailPrice() * max(1, SendMail_NumAttachments()) <= GetMoney() then - MailMailButton:Enable() - else - MailMailButton:Disable() - end -end - -function SendMail_Attached(bag, slot) - if not MailFrame:IsVisible() then return false end - for i = 1, ATTACHMENTS_MAX do - local btn = _G['MailAttachment' .. i] - if btn.item and btn.item[1] == bag and btn.item[2] == slot then - return true - end - end - if SendMail_state then - for _, attachment in SendMail_state.attachments do - if attachment[1] == bag and attachment[2] == slot then - return true - end - end - end -end - -function AttachmentButton_OnClick() - local attachedItem = this.item - local cursorItem = GetCursorItem() - if SendMail_SetAttachment(cursorItem, this) then - if attachedItem then - if arg1 == 'LeftButton' then SetCursorItem(attachedItem) end - orig.PickupContainerItem(unpack(attachedItem)) - if arg1 ~= 'LeftButton' then ClearCursor() end -- for the lock changed event - end - end -end - --- requires an item lock changed event for a proper update -function SendMail_SetAttachment(item, slot) - if item and not SendMail_PickupMailable(item) then - ClearCursor() - return - elseif not slot then - for i = 1, ATTACHMENTS_MAX do - if not _G['MailAttachment' .. i].item then - slot = _G['MailAttachment' .. i] - break - end - end - end - if slot then - if not (item or slot.item) then return true end - slot.item = item - ClearCursor() - SendMailFrame_Update() - return true - end -end - -function SendMail_PickupMailable(item) - ClearCursor() - orig.ClickSendMailItemButton() - ClearCursor() - orig.PickupContainerItem(unpack(item)) - orig.ClickSendMailItemButton() - local mailable = GetSendMailItem() and true or false - orig.ClickSendMailItemButton() - return mailable -end - -function SendMail_NumAttachments() - local x = 0 - for i = 1, ATTACHMENTS_MAX do - if _G['MailAttachment' .. i].item then - x = x + 1 - end - end - return x -end - -function SendMail_Attachments() - local t = {} - for i = 1, ATTACHMENTS_MAX do - local btn = _G['MailAttachment' .. i] - if btn.item then - tinsert(t, btn.item) - end - end - return t -end - -function SendMail_Clear() - local anyItem - for i = 1, ATTACHMENTS_MAX do - anyItem = anyItem or _G['MailAttachment' .. i].item - _G['MailAttachment' .. i].item = nil - end - if anyItem then - ClearCursor() - PickupContainerItem(unpack(anyItem)) - ClearCursor() - end - MailMailButton:Disable() - SendMailNameEditBox:SetText'' - SendMailNameEditBox:SetFocus() - MailSubjectEditBox:SetText'' - SendMailBodyEditBox:SetText'' - MoneyInputFrame_ResetMoney(SendMailMoney) - SendMailRadioButton_OnClick(1) - - SendMailFrame_Update() -end - -function SendMail_Send() - local item = tremove(SendMail_state.attachments, 1) - if item then - ClearCursor() - orig.ClickSendMailItemButton() - ClearCursor() - orig.PickupContainerItem(unpack(item)) - orig.ClickSendMailItemButton() - - if not GetSendMailItem() then - DEFAULT_CHAT_FRAME:AddMessage('Mail: ' .. ERROR_CAPS, 1, 0, 0) - return - end - end - - local amount = SendMail_state.money - if amount > 0 then - SendMail_state.money = 0 - if SendMail_state.cod then - SetSendMailCOD(amount) - else - SetSendMailMoney(amount) - end - end - - local subject = SendMail_state.subject - if subject == '' then - if item then - local itemName, itemTexture, stackCount, quality = GetSendMailItem() - subject = itemName .. (stackCount > 1 and ' (' .. stackCount .. ')' or '') - else - subject = '<' .. NO_ATTACHMENTS .. '>' - end - elseif SendMail_state.numMessages > 1 then - subject = subject .. format(' [%d/%d]', SendMail_state.numMessages - getn(SendMail_state.attachments), SendMail_state.numMessages) - end - - SendMail(SendMail_state.to, subject, SendMail_state.body) - - if getn(SendMail_state.attachments) == 0 then - SendMail_sending = false - end -end - -do - local inputLength - local matches = {} - local index - - local function complete() - SendMailNameEditBox:SetText(matches[index]) - SendMailNameEditBox:HighlightText(inputLength, -1) - for i = 1, MAIL_AUTOCOMPLETE_MAX_BUTTONS do - local button = _G['MailAutoCompleteButton' .. i] - if i == index then - button:LockHighlight() - else - button:UnlockHighlight() - end - end - end - - function PreviousMatch() - if index then - index = index > 1 and index - 1 or getn(matches) - complete() - end - end - - function NextMatch() - if index then - index = mod(index, getn(matches)) + 1 - complete() - end - end - - function SelectMatch(i) - index = i - complete() - MailAutoCompleteBox:Hide() - SendMailNameEditBox:HighlightText(0, 0) - end - - function GetSuggestions() - local input = SendMailNameEditBox:GetText() - inputLength = strlen(input) - - table.setn(matches, 0) - index = nil - - local autoCompleteNames = {} - for name, time in Mail_AutoCompleteNames[GetCVar'realmName' .. '|' .. UnitFactionGroup'player'] do - tinsert(autoCompleteNames, {name=name, time=time}) - end - sort(autoCompleteNames, function(a, b) return b.time < a.time end) - - local ignore = {[UnitName'player']=true} - local function process(name) - if name then - if not ignore[name] and strfind(strupper(name), strupper(input), nil, true) == 1 then - tinsert(matches, name) - end - ignore[name] = true - end - end - for _, t in autoCompleteNames do - process(t.name) - end - for i = 1, GetNumFriends() do - process(GetFriendInfo(i)) - end - for i = 1, GetNumGuildMembers(true) do - process(GetGuildRosterInfo(i)) - end - - table.setn(matches, min(getn(matches), MAIL_AUTOCOMPLETE_MAX_BUTTONS)) - if getn(matches) > 0 and (getn(matches) > 1 or input ~= matches[1]) then - for i = 1, MAIL_AUTOCOMPLETE_MAX_BUTTONS do - local button = _G['MailAutoCompleteButton' .. i] - if i <= getn(matches) then - button:SetText(matches[i]) - button:GetFontString():SetPoint('LEFT', button, 'LEFT', 15, 0) - button:Show() - else - button:Hide() - end - end - MailAutoCompleteBox:SetHeight(getn(matches) * MailAutoCompleteButton1:GetHeight() + 35) - MailAutoCompleteBox:SetWidth(120) - MailAutoCompleteBox:Show() - index = 1 - complete() - else - MailAutoCompleteBox:Hide() - end - end -end diff --git a/Mail.toc b/Mail.toc deleted file mode 100644 index 647b424..0000000 --- a/Mail.toc +++ /dev/null @@ -1,7 +0,0 @@ -## Interface: 11200 -## Title: Mail -## Notes: Mailbox enhancement -## Author: shirsig -## SavedVariablesPerCharacter: Mail_To -## SavedVariables: Mail_AutoCompleteNames -Mail.xml \ No newline at end of file diff --git a/README.md b/README.md index f9c2e95..9df6a0a 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,18 @@ -# Mail - WoW 1.12 addOn +# TurtleMail - WoW 1.12 addOn An extension to the Blizzard mail interface which - **Automatically opens mail, very rapidly** - **Mails multiple items at once, very rapidly** - **Autocompletes recipient names** +- **Shows collected gold from opened mails** +- **Apply COD to 1st or all mails** **\** on inbox items to loot the gold, loot the item and destroy the letter, in that order, if any.
**\** or **\** to add inventory items to the attachments.
**\** to add inventory items to the trade frame. -Note that due to limitations of the 1.12 API, behind the scenes, the items must be sent in separate mails.
-As a result of this **COD will only apply to the first item** if multiple items are added. +Note that COD is always ignored when opening, both automatically as well as by **\**. -Also note that COD is always ignored when opening, both automatically as well as by **\**. +![Alt text](https://i.imgur.com/H0MUmXd.png) -![Alt text](http://i.imgur.com/H0MUmXd.png) - -![Alt text](http://i.imgur.com/ZDyfMIK.png) +![Alt text](https://i.imgur.com/LM7tRcx.png) diff --git a/TurtleMail.lua b/TurtleMail.lua new file mode 100644 index 0000000..3f6458b --- /dev/null +++ b/TurtleMail.lua @@ -0,0 +1,998 @@ +Mail = Mail or {} +local m = Mail + +local ATTACHMENTS_MAX = 21 +local ATTACHMENTS_PER_ROW_SEND = 7 +local ATTACHMENTS_MAX_ROWS_SEND = 3 + +local api = getfenv() +local getn = table.getn ---@diagnostic disable-line: deprecated +local money_received = 0 +local orig = {} +local L = {} + +do + local f = api.CreateFrame( "Frame" ) + f:SetScript( "OnEvent", function() Mail[ event ]() end ) + + for _, event in { "ADDON_LOADED", "PLAYER_LOGIN", "UI_ERROR_MESSAGE", "CURSOR_UPDATE", "BAG_UPDATE", "MAIL_SHOW", "MAIL_CLOSED", "MAIL_SEND_SUCCESS", "MAIL_INBOX_UPDATE" } do + f:RegisterEvent( event ) + end +end + +api.TurtleMail_AutoCompleteNames = {} + +local function pack( ... ) return arg end + +do + local f = api.CreateFrame( "Frame" ) + local cursorItem + function m.CURSOR_UPDATE() + cursorItem = nil + end + + function GetCursorItem() + return cursorItem + end + + function SetCursorItem( item ) + f:SetScript( "OnUpdate", function() + cursorItem = item + f:SetScript( "OnUpdate", nil ) + end ) + end +end + +function m.BAG_UPDATE() + if api.MailFrame:IsVisible() then + api.SendMailFrame_Update() + end +end + +function m.MAIL_SHOW() + if api.TurtleMail_Point then + api.MailFrame:SetPoint( api.TurtleMail_Point.point, api.TurtleMail_Point.x, api.TurtleMail_Point.y ) + end +end + +function m.MAIL_CLOSED() + Inbox_Abort() + SendMail_sending = false + SendMail_Clear() +end + +function m.UI_ERROR_MESSAGE() + if Inbox_opening then + if arg1 == api.ERR_INV_FULL then + Inbox_Abort() + elseif arg1 == api.ERR_ITEM_MAX_COUNT then + Inbox_skip = true + end + elseif SendMail_sending and (arg1 == api.ERR_MAIL_TO_SELF or arg1 == api.ERR_PLAYER_WRONG_FACTION or arg1 == api.ERR_MAIL_TARGET_NOT_FOUND or arg1 == api.ERR_MAIL_REACHED_CAP) then + SendMail_sending = false + SendMail_state = nil + api.ClearCursor() + orig.ClickSendMailItemButton() + api.ClearCursor() + end +end + +function m.ADDON_LOADED() + if arg1 ~= "TurtleMail" then return end + + local version = api.GetAddOnMetadata( "TurtleMail", "Version" ) + api.DEFAULT_CHAT_FRAME:AddMessage( string.format( "|cffabd473TurtleMail|r: Loaded(|cffeda55fv%s|r).", version ) ) + + api.UIPanelWindows.MailFrame.pushable = 1 + api.UIPanelWindows.FriendsFrame.pushable = 2 + + Inbox_Load() + SendMail_Load() +end + +do + local f = api.CreateFrame( "Frame" ) + local function update() + f:SetScript( "OnUpdate", nil ) + SendMail_Send() + end + function m.MAIL_SEND_SUCCESS() + if SendMail_state then + m.addAutoCompleteName( SendMail_state.to ) + end + if SendMail_sending then + f:SetScript( "OnUpdate", update ) + end + end +end + +function m.addAutoCompleteName( name ) + local key = api.GetCVar "realmName" .. "|" .. api.UnitFactionGroup "player" + api.TurtleMail_AutoCompleteNames[ key ][ name ] = api.GetTime() +end + +do + local x = 0 + api.CreateFrame( "Frame", api.InboxFrame ):SetScript( "OnUpdate", function() + if x > 0 then + x = x - 1 + elseif not Inbox_opening then + x = 100 + api.CheckInbox() + end + end ) +end + +function Inbox_Load() + local btn = api.CreateFrame( "Button", nil, api.InboxFrame, "UIPanelButtonTemplate" ) + btn:SetPoint( "BOTTOM", -10, 90 ) + btn:SetText( api.OPENMAIL ) + btn:SetWidth( math.max( 120, 30 + ({ btn:GetRegions() })[ 1 ]:GetStringWidth() ) ) + btn:SetHeight( 25 ) + btn:SetScript( "OnClick", Inbox_OpenAll ) + + api.MailFrame:EnableMouse( true ) + api.MailFrame:SetMovable( true ) + api.MailFrame:SetScript( "OnDragStop", m.on_drag_stop ) +end + +do + local i, update + local f = api.CreateFrame( "Frame" ) + f:Hide() + f:SetScript( "OnUpdate", function() + if update then + update = false + local _, _, _, _, _, COD, _, _, _, _, _, _, isGM = api.GetInboxHeaderInfo( i ) + if i > api.GetInboxNumItems() then + if money_received > 0 then + api.DEFAULT_CHAT_FRAME:AddMessage( string.format( "|cffabd473TurtleMail|r: %s%s.", m.format_money( money_received ), L[ "collected" ] ) ) + end + Inbox_Abort() + elseif Inbox_Skip or COD > 0 or isGM then + Inbox_Skip = false + i = i + 1 + update = true + else + Inbox_Open( i ) + end + end + end ) + + function m.MAIL_INBOX_UPDATE() + if Inbox_opening then + update = true + end + end + + function Inbox_OpenAll() + Inbox_opening = true + Inbox_UpdateLock() + i = 1 + Inbox_skip = false + update = true + f:Show() + end + + function Inbox_Abort() + Inbox_opening = false + Inbox_UpdateLock() + f:Hide() + end +end + +function m.set_cod_text() + local text = string.sub( api.COD_AMOUNT, 1, string.len( api.COD_AMOUNT ) - 1 ) + + ---@diagnostic disable-next-line: undefined-global + if not pfUI or not pfUI.version then + text = string.match( text, "^(.-)%s+%S+$" ) + end + + if api.SendMailCODAllButton:GetChecked() then + api.SendMailMoneyText:SetText( text .. " " .. L[ "each mail" ] .. ":" ) + else + api.SendMailMoneyText:SetText( text .. " " .. L[ "1st mail" ] .. ":" ) + end +end + +---@param copper number +function m.format_money( copper ) + local gold = math.floor( copper / 10000 ) + local silver = math.floor( (copper - gold * 10000) / 100 ) + local copper_remain = copper - (gold * 10000) - (silver * 100) + + local result = "" + if gold > 0 then + result = result .. string.format( "|cffffffff%d|cffffd700g|r ", gold ) + end + if silver > 0 then + result = result .. string.format( "|cffffffff%d|cffc7c7cfs|r ", silver ) + end + if copper_remain > 0 or result == "" then + result = result .. string.format( "|cffffffff%d|cffeda55fc|r ", copper_remain ) + end + + return result +end + +---@param money number +function m.update_money( money ) + money_received = money_received + money + api.MoneyReceived:SetText( L[ "Money received" ] .. ": " .. m.format_money( money_received ) ) + + if money_received > 0 then + api.MoneyReceived:Show() + else + api.MoneyReceived:Hide() + end +end + +function m.on_drag_stop() + local f = api.MailFrame + if not f then return end + f:StopMovingOrSizing() + + local screen_width = api.GetScreenWidth() + local screen_height = api.GetScreenHeight() + local point, _, _, x, y = api.MailFrame:GetPoint() + local nx, ny = x, y + + if f:GetLeft() < 0 then nx = -10 end + if f:GetRight() > screen_width + 28 then nx = screen_width + 28 - f:GetWidth() end + if f:GetTop() > screen_height + 10 then ny = 10 end + if f:GetBottom() < -40 then ny = -screen_height + f:GetHeight() - 40 end + + if (nx ~= x or ny ~= y) then + api.MailFrame:SetPoint( point, nx, ny ) + end + + api.TurtleMail_Point = { point = point, x = nx, y = ny } +end + +do + -- hack to prevent beancounter from deleting mail + local TakeInboxMoney, TakeInboxItem, DeleteInboxItem = api.TakeInboxMoney, api.TakeInboxItem, api.DeleteInboxItem + function Inbox_Open( i ) + local _, _, _, _, money, _, _, _, read, _, _, _, _ = api.GetInboxHeaderInfo( i ) + if money and read then + m.update_money( money ) + end + api.GetInboxText( i ) + + TakeInboxMoney( i ) + TakeInboxItem( i ) + DeleteInboxItem( i ) + end +end + +function Inbox_UpdateLock() + for i = 1, 7 do + api[ "MailItem" .. i .. "ButtonIcon" ]:SetDesaturated( Inbox_opening ) + if Inbox_opening then + api[ "MailItem" .. i .. "Button" ]:SetChecked( nil ) + end + end +end + +-- Setup hooks +do + local hooks = {} + m.hook = setmetatable( {}, { __newindex = function( _, k, v ) hooks[ k ] = v end } ) + function m.PLAYER_LOGIN() + for k, v in hooks do + orig[ k ] = api[ k ] + api[ k ] = v + end + local key = api.GetCVar "realmName" .. "|" .. api.UnitFactionGroup "player" + api.TurtleMail_AutoCompleteNames[ key ] = api.TurtleMail_AutoCompleteNames[ key ] or {} + for char, lastSeen in api.TurtleMail_AutoCompleteNames[ key ] do + if api.GetTime() - lastSeen > 60 * 60 * 24 * 30 then + api.TurtleMail_AutoCompleteNames[ key ][ char ] = nil + end + end + + m.addAutoCompleteName( api.UnitName "player" ) + end +end + +function m.hook.GetInboxHeaderInfo( ... ) + local sender, canReply = arg[ 3 ], arg[ 12 ] + if sender and canReply then + m.addAutoCompleteName( sender ) + end + return orig.GetInboxHeaderInfo( unpack( arg ) ) +end + +function m.hook.OpenMail_Reply( ... ) + api.TurtleMail_To = nil + return orig.OpenMail_Reply( unpack( arg ) ) +end + +function m.hook.InboxFrame_Update() + orig.InboxFrame_Update() + for i = 1, 7 do + -- hack for tooltip update + api[ "MailItem" .. i ]:Hide() + api[ "MailItem" .. i ]:Show() + end + + do + local currentPage = api.InboxFrame.pageNum + local totalPages = math.ceil( api.GetInboxNumItems() / api.INBOXITEMS_TO_DISPLAY ) + local text = totalPages > 0 and (currentPage .. "/" .. totalPages) or ("Empty") + api.InboxTitleText:SetText( "Inbox [" .. text .. "]" ) + end + + Inbox_UpdateLock() +end + +function m.hook.InboxFrame_OnClick( i ) + if Inbox_opening or arg1 == "RightButton" and ({ api.GetInboxHeaderInfo( i ) })[ 6 ] > 0 then + this:SetChecked( nil ) + elseif arg1 == "RightButton" then + Inbox_Open( i ) + else + return orig.InboxFrame_OnClick( i ) + end +end + +function m.hook.InboxFrameItem_OnEnter() + orig.InboxFrameItem_OnEnter() + if api.GetInboxItem( this.index ) then + api.GameTooltip:AddLine( api.ITEM_OPENABLE, "", 0, 1, 0 ) + api.GameTooltip:Show() + end +end + +function m.hook.SendMailFrame_Update() + local gap + -- local last = 0 blizzlike + local last = SendMail_NumAttachments() + + for i = 1, ATTACHMENTS_MAX do + local btn = api[ "MailAttachment" .. i ] + + local texture, count + if btn.item then + texture, count = api.GetContainerItemInfo( unpack( btn.item ) ) + end + if not texture then + btn:SetNormalTexture( nil ) + api[ btn:GetName() .. "Count" ]:Hide() + btn.item = nil + else + btn:SetNormalTexture( texture ) + if count > 1 then + api[ btn:GetName() .. "Count" ]:Show() + api[ btn:GetName() .. "Count" ]:SetText( count ) + else + api[ btn:GetName() .. "Count" ]:Hide() + end + end + end + + if SendMail_NumAttachments() > 0 then + api.SendMailCODButton:Enable() + api.SendMailCODButtonText:SetTextColor( api.NORMAL_FONT_COLOR.r, api.NORMAL_FONT_COLOR.g, api.NORMAL_FONT_COLOR.b ) + if SendMail_NumAttachments() > 1 and api.SendMailCODButton:GetChecked() then + api.SendMailCODAllButton:Enable() + api.SendMailCODAllButtonText:SetTextColor( api.NORMAL_FONT_COLOR.r, api.NORMAL_FONT_COLOR.g, api.NORMAL_FONT_COLOR.b ) + m.set_cod_text() + else + api.SendMailCODAllButton:Disable() + api.SendMailCODAllButtonText:SetTextColor( api.GRAY_FONT_COLOR.r, api.GRAY_FONT_COLOR.g, api.GRAY_FONT_COLOR.b ) + api.SendMailMoneyText:SetText( api.COD_AMOUNT ) + end + else + api.SendMailSendMoneyButton:SetChecked( 1 ) + api.SendMailCODButton:SetChecked( nil ) + api.SendMailMoneyText:SetText( api.AMOUNT_TO_SEND ) + api.SendMailCODButton:Disable() + api.SendMailCODButtonText:SetTextColor( api.GRAY_FONT_COLOR.r, api.GRAY_FONT_COLOR.g, api.GRAY_FONT_COLOR.b ) + api.SendMailCODAllButton:Disable() + api.SendMailCODAllButtonText:SetTextColor( api.GRAY_FONT_COLOR.r, api.GRAY_FONT_COLOR.g, api.GRAY_FONT_COLOR.b ) + end + + api.MoneyFrame_Update( "SendMailCostMoneyFrame", api.GetSendMailPrice() * math.max( 1, SendMail_NumAttachments() ) ) + + -- Determine how many rows of attachments to show + local itemRowCount = 1 + local temp = last + while temp > ATTACHMENTS_PER_ROW_SEND and itemRowCount < ATTACHMENTS_MAX_ROWS_SEND do + itemRowCount = itemRowCount + 1 + temp = temp - ATTACHMENTS_PER_ROW_SEND + end + + if not gap and temp == ATTACHMENTS_PER_ROW_SEND and itemRowCount < ATTACHMENTS_MAX_ROWS_SEND then + itemRowCount = itemRowCount + 1 + end + if api.SendMailFrame.maxRowsShown and last > 0 and itemRowCount < api.SendMailFrame.maxRowsShown then + itemRowCount = api.SendMailFrame.maxRowsShown + else + api.SendMailFrame.maxRowsShown = itemRowCount + end + + -- Compute sizes + local cursorx = 0 + local cursory = itemRowCount - 1 + local marginxl = 8 + 6 + local marginxr = 40 + 6 + local areax = api.SendMailFrame:GetWidth() - marginxl - marginxr + local iconx = api.MailAttachment1:GetWidth() + 2 + local icony = api.MailAttachment1:GetHeight() + 2 + local gapx1 = api.floor( (areax - (iconx * ATTACHMENTS_PER_ROW_SEND)) / (ATTACHMENTS_PER_ROW_SEND - 1) ) + local gapx2 = api.floor( (areax - (iconx * ATTACHMENTS_PER_ROW_SEND) - (gapx1 * (ATTACHMENTS_PER_ROW_SEND - 1))) / 2 ) + local gapy1 = 5 + local gapy2 = 6 + local areay = (gapy2 * 2) + (gapy1 * (itemRowCount - 1)) + (icony * itemRowCount) + local indentx = marginxl + gapx2 + 17 + local indenty = 170 + gapy2 + icony - 13 + local tabx = (iconx + gapx1) - 3 --this magic number changes the attachment spacing + local taby = (icony + gapy1) + local scrollHeight = 249 - areay + + api.MailHorizontalBarLeft:SetPoint( "TOPLEFT", api.SendMailFrame, "BOTTOMLEFT", 2 + 15, 184 + areay - 14 ) + + api.SendMailScrollFrame:SetHeight( scrollHeight ) + api.SendMailScrollChildFrame:SetHeight( scrollHeight ) + + local SendMailScrollFrameTop = ({ api.SendMailScrollFrame:GetRegions() })[ 3 ] + SendMailScrollFrameTop:SetHeight( scrollHeight ) + SendMailScrollFrameTop:SetTexCoord( 0, .484375, 0, scrollHeight / 256 ) + + api.StationeryBackgroundLeft:SetHeight( scrollHeight ) + api.StationeryBackgroundLeft:SetTexCoord( 0, 1, 0, scrollHeight / 256 ) + + + api.StationeryBackgroundRight:SetHeight( scrollHeight ) + api.StationeryBackgroundRight:SetTexCoord( 0, 1, 0, scrollHeight / 256 ) + + -- Set Items + for i = 1, ATTACHMENTS_MAX do + if cursory >= 0 then + api[ "MailAttachment" .. i ]:Enable() + api[ "MailAttachment" .. i ]:Show() + api[ "MailAttachment" .. i ]:SetPoint( "TOPLEFT", "SendMailFrame", "BOTTOMLEFT", indentx + (tabx * cursorx), + indenty + (taby * cursory) ) + + cursorx = cursorx + 1 + if cursorx >= ATTACHMENTS_PER_ROW_SEND then + cursory = cursory - 1 + cursorx = 0 + end + else + api[ "MailAttachment" .. i ]:Hide() + end + end + + api.SendMailFrame_CanSend() +end + +function m.hook.SendMailRadioButton_OnClick( index ) + if (index == 1) then + api.SendMailSendMoneyButton:SetChecked( 1 ); + api.SendMailCODButton:SetChecked( nil ); + api.SendMailMoneyText:SetText( api.AMOUNT_TO_SEND ); + api.SendMailCODAllButton:Disable() + api.SendMailCODAllButtonText:SetTextColor( api.GRAY_FONT_COLOR.r, api.GRAY_FONT_COLOR.g, api.GRAY_FONT_COLOR.b ) + else + api.SendMailSendMoneyButton:SetChecked( nil ); + api.SendMailCODButton:SetChecked( 1 ); + api.SendMailMoneyText:SetText( api.COD_AMOUNT ); + + if SendMail_NumAttachments() > 1 then + api.SendMailCODAllButton:Enable() + api.SendMailCODAllButtonText:SetTextColor( api.NORMAL_FONT_COLOR.r, api.NORMAL_FONT_COLOR.g, api.NORMAL_FONT_COLOR.b ) + m.set_cod_text() + end + end + api.PlaySound( "igMainMenuOptionCheckBoxOn" ); +end + +function m.hook.ClickSendMailItemButton() + SendMail_SetAttachment( GetCursorItem() ) +end + +function m.hook.GetContainerItemInfo( bag, slot ) + local ret = pack( orig.GetContainerItemInfo( bag, slot ) ) + ret[ 3 ] = ret[ 3 ] or SendMail_Attached( bag, slot ) and 1 or nil + return unpack( ret ) +end + +function m.hook.PickupContainerItem( bag, slot ) + if SendMail_Attached( bag, slot ) then return end + if api.GetContainerItemInfo( bag, slot ) then SetCursorItem( { bag, slot } ) end + return orig.PickupContainerItem( bag, slot ) +end + +function m.hook.SplitContainerItem( bag, slot, amount ) + if SendMail_Attached( bag, slot ) then return end + return orig.SplitContainerItem( bag, slot, amount ) +end + +function m.hook.UseContainerItem( bag, slot, onself ) + if SendMail_Attached( bag, slot ) then return end + if api.IsShiftKeyDown() or api.IsControlKeyDown() or api.IsAltKeyDown() then + return orig.UseContainerItem( bag, slot, onself ) + elseif api.MailFrame:IsVisible() then + api.MailFrameTab_OnClick( 2 ) + SendMail_SetAttachment { bag, slot } + elseif api.TradeFrame:IsVisible() then + for i = 1, 6 do + if not api.GetTradePlayerItemLink( i ) then + orig.PickupContainerItem( bag, slot ) + api.ClickTradeButton( i ) + return + end + end + else + return orig.UseContainerItem( bag, slot, onself ) + end +end + +function m.hook.SendMailFrame_CanSend() + if not SendMail_sending and string.len( api.SendMailNameEditBox:GetText() ) > 0 and (api.SendMailSendMoneyButton:GetChecked() and api.MoneyInputFrame_GetCopper( api.SendMailMoney ) or 0) + api.GetSendMailPrice() * math.max( 1, SendMail_NumAttachments() ) <= api.GetMoney() then + MailMailButton:Enable() + else + MailMailButton:Disable() + end +end + +function MailMailButton_OnClick() + api.MailAutoCompleteBox:Hide() + + api.TurtleMail_To = api.SendMailNameEditBox:GetText() + api.SendMailNameEditBox:HighlightText() + + SendMail_state = { + to = api.TurtleMail_To, + subject = MailSubjectEditBox:GetText(), + body = api.SendMailBodyEditBox:GetText(), + money = api.MoneyInputFrame_GetCopper( api.SendMailMoney ), + cod = api.SendMailCODButton:GetChecked(), + attachments = SendMail_Attachments(), + numMessages = math.max( 1, SendMail_NumAttachments() ), + } + + SendMail_Clear() + SendMail_sending = true + SendMail_Send() +end + +function SendMail_Load() + api.SendMailFrame:CreateTexture( "MailHorizontalBarLeft", "BACKGROUND" ) + api.SendMailFrame:CreateTexture( "MailHorizontalBarRight", "BACKGROUND" ) + ---@diagnostic disable-next-line: undefined-global + if not pfUI or not pfUI.version then + api.MailHorizontalBarLeft:SetTexture( [[Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar]] ) + api.MailHorizontalBarLeft:SetWidth( 256 ) + api.MailHorizontalBarLeft:SetHeight( 16 ) + api.MailHorizontalBarLeft:SetTexCoord( 0, 1, 0, .25 ) + + api.MailHorizontalBarRight:SetTexture( [[Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar]] ) + api.MailHorizontalBarRight:SetWidth( 75 ) + api.MailHorizontalBarRight:SetHeight( 16 ) + api.MailHorizontalBarRight:SetTexCoord( 0, .29296875, .25, .5 ) + api.MailHorizontalBarRight:SetPoint( "LEFT", api.MailHorizontalBarLeft, "RIGHT" ) + end + + do + local background = ({ api.SendMailPackageButton:GetRegions() })[ 1 ] + background:Hide() + local count = ({ api.SendMailPackageButton:GetRegions() })[ 3 ] + count:Hide() + api.SendMailPackageButton:Disable() + api.SendMailPackageButton:SetScript( "OnReceiveDrag", nil ) + api.SendMailPackageButton:SetScript( "OnDragStart", nil ) + end + + api.SendMailMoneyText:SetJustifyH( "LEFT" ) + api.SendMailMoneyText:SetPoint( "TOPLEFT", 0, 0 ) + api.SendMailMoney:ClearAllPoints() + api.SendMailMoney:SetPoint( "TOPLEFT", api.SendMailMoneyText, "BOTTOMLEFT", 5, -5 ) + api.SendMailMoneyGoldRight:SetPoint( "RIGHT", 20, 0 ) + do ({ api.SendMailMoneyGold:GetRegions() })[ 9 ]:SetDrawLayer( "BORDER" ) end + api.SendMailMoneyGold:SetMaxLetters( 7 ) + api.SendMailMoneyGold:SetWidth( 50 ) + api.SendMailMoneySilverRight:SetPoint( "RIGHT", 10, 0 ) + do ({ api.SendMailMoneySilver:GetRegions() })[ 9 ]:SetDrawLayer( "BORDER" ) end + api.SendMailMoneySilver:SetWidth( 28 ) + api.SendMailMoneySilver:SetPoint( "LEFT", api.SendMailMoneyGold, "RIGHT", 30, 0 ) + api.SendMailMoneyCopperRight:SetPoint( "RIGHT", 10, 0 ) + do ({ api.SendMailMoneyCopper:GetRegions() })[ 9 ]:SetDrawLayer( "BORDER" ) end + api.SendMailMoneyCopper:SetWidth( 28 ) + api.SendMailMoneyCopper:SetPoint( "LEFT", api.SendMailMoneySilver, "RIGHT", 20, 0 ) + api.SendMailSendMoneyButton:SetPoint( "TOPLEFT", api.SendMailMoney, "TOPRIGHT", 0, 12 ) + + -- hack to avoid automatic subject setting and button disabling from weird blizzard code + MailMailButton = api.SendMailMailButton + api.SendMailMailButton = setmetatable( {}, { __index = function() return function() end end } ) + api.SendMailMailButton_OnClick = MailMailButton_OnClick + MailSubjectEditBox = api.SendMailSubjectEditBox + api.SendMailSubjectEditBox = setmetatable( {}, { + __index = function( _, key ) + return function( _, ... ) + return MailSubjectEditBox[ key ]( MailSubjectEditBox, unpack( arg ) ) + end + end, + } ) + + api.SendMailNameEditBox._SetText = api.SendMailNameEditBox.SetText + function api.SendMailNameEditBox:SetText( ... ) + if not api.TurtleMail_To then + return self:_SetText( unpack( arg ) ) + end + end + + api.SendMailNameEditBox:SetScript( "OnShow", function() + if api.TurtleMail_To then + api.this:_SetText( api.TurtleMail_To ) + end + end ) + api.SendMailNameEditBox:SetScript( "OnChar", function() + api.TurtleMail_To = nil + GetSuggestions() + end ) + api.SendMailNameEditBox:SetScript( "OnTabPressed", function() + if api.MailAutoCompleteBox:IsVisible() then + if api.IsShiftKeyDown() then + PreviousMatch() + else + NextMatch() + end + else + MailSubjectEditBox:SetFocus() + end + end ) + api.SendMailNameEditBox:SetScript( "OnEnterPressed", function() + if api.MailAutoCompleteBox:IsVisible() then + api.MailAutoCompleteBox:Hide() + this:HighlightText( 0, 0 ) + else + MailSubjectEditBox:SetFocus() + end + end ) + api.SendMailNameEditBox:SetScript( "OnEscapePressed", function() + if api.MailAutoCompleteBox:IsVisible() then + api.MailAutoCompleteBox:Hide() + else + this:ClearFocus() + end + end ) + function api.SendMailNameEditBox.focusLoss() + api.MailAutoCompleteBox:Hide() + end + + api.SendMailCODAllButton:SetScript( "OnClick", function() + m.set_cod_text() + end ) + + do + local orig_script = api.SendMailNameEditBox:GetScript( "OnTextChanged" ) + api.SendMailNameEditBox:SetScript( "OnTextChanged", function() + local text = this:GetText() + local formatted = string.gsub( string.lower( text ), "^%l", string.upper ) + if text ~= formatted then + this:SetText( formatted ) + end + return orig_script() + end ) + end + + for _, editBox in { api.SendMailNameEditBox, api.SendMailSubjectEditBox } do + editBox:SetScript( "OnEditFocusGained", function() + this:HighlightText() + end ) + editBox:SetScript( "OnEditFocusLost", function() + (this.focusLoss or function() end)() + this:HighlightText( 0, 0 ) + end ) + do + local lastClick + editBox:SetScript( "OnMouseDown", function() + local x, y = api.GetCursorPosition() + if lastClick and api.GetTime() - lastClick.t < .5 and x == lastClick.x and y == lastClick.y then + this:SetScript( "OnUpdate", function() + this:HighlightText() + this:SetScript( "OnUpdate", nil ) + end ) + end + lastClick = { t = api.GetTime(), x = x, y = y } + end ) + end + end +end + +function SendMail_Attached( bag, slot ) + if not api.MailFrame:IsVisible() then return false end + for i = 1, ATTACHMENTS_MAX do + local btn = api[ "MailAttachment" .. i ] + if btn.item and btn.item[ 1 ] == bag and btn.item[ 2 ] == slot then + return true + end + end + if SendMail_state then + for _, attachment in SendMail_state.attachments do + if attachment[ 1 ] == bag and attachment[ 2 ] == slot then + return true + end + end + end +end + +function m.AttachmentButton_OnClick() + local attachedItem = this.item + local cursorItem = GetCursorItem() + if SendMail_SetAttachment( cursorItem, this ) then + if attachedItem then + if arg1 == "LeftButton" then SetCursorItem( attachedItem ) end + orig.PickupContainerItem( unpack( attachedItem ) ) + if arg1 ~= "LeftButton" then api.ClearCursor() end -- for the lock changed event + end + end +end + +-- requires an item lock changed event for a proper update +function SendMail_SetAttachment( item, slot ) + if item and not SendMail_PickupMailable( item ) then + api.ClearCursor() + return + elseif not slot then + for i = 1, ATTACHMENTS_MAX do + if not api[ "MailAttachment" .. i ].item then + slot = api[ "MailAttachment" .. i ] + break + end + end + end + if slot then + if not (item or slot.item) then return true end + slot.item = item + api.ClearCursor() + api.SendMailFrame_Update() + return true + end +end + +function SendMail_PickupMailable( item ) + api.ClearCursor() + orig.ClickSendMailItemButton() + api.ClearCursor() + orig.PickupContainerItem( unpack( item ) ) + orig.ClickSendMailItemButton() + local mailable = api.GetSendMailItem() and true or false + orig.ClickSendMailItemButton() + return mailable +end + +function SendMail_NumAttachments() + local x = 0 + for i = 1, ATTACHMENTS_MAX do + if api[ "MailAttachment" .. i ].item then + x = x + 1 + end + end + return x +end + +function SendMail_Attachments() + local t = {} + for i = 1, ATTACHMENTS_MAX do + local btn = api[ "MailAttachment" .. i ] + if btn.item then + table.insert( t, btn.item ) + end + end + return t +end + +function SendMail_Clear() + local anyItem + for i = 1, ATTACHMENTS_MAX do + anyItem = anyItem or api[ "MailAttachment" .. i ].item + api[ "MailAttachment" .. i ].item = nil + end + if anyItem then + api.ClearCursor() + api.PickupContainerItem( unpack( anyItem ) ) + api.ClearCursor() + end + MailMailButton:Disable() + api.SendMailNameEditBox:SetText "" + api.SendMailNameEditBox:SetFocus() + MailSubjectEditBox:SetText "" + api.SendMailBodyEditBox:SetText "" + api.MoneyInputFrame_ResetMoney( api.SendMailMoney ) + api.SendMailRadioButton_OnClick( 1 ) + + api.SendMailFrame_Update() +end + +function SendMail_Send() + local item = table.remove( SendMail_state.attachments, 1 ) + if item then + api.ClearCursor() + orig.ClickSendMailItemButton() + api.ClearCursor() + orig.PickupContainerItem( unpack( item ) ) + orig.ClickSendMailItemButton() + + if not api.GetSendMailItem() then + api.DEFAULT_CHAT_FRAME:AddMessage( "|cffabd473TurtleMail|r: " .. api.ERROR_CAPS, 1, 0, 0 ) + return + end + end + + local amount = SendMail_state.money + if amount > 0 then + if not api.SendMailCODAllButton:GetChecked() then + SendMail_state.money = 0 + end + if SendMail_state.cod then + api.SetSendMailCOD( amount ) + else + SendMail_state.money = 0 + api.SetSendMailMoney( amount ) + end + end + + local subject = SendMail_state.subject + if subject == "" then + if item then + local itemName, itemTexture, stackCount, quality = api.GetSendMailItem() + subject = itemName .. (stackCount > 1 and " (" .. stackCount .. ")" or "") + else + subject = "<" .. api.NO_ATTACHMENTS .. ">" + end + elseif SendMail_state.numMessages > 1 then + subject = subject .. + string.format( " [%d/%d]", SendMail_state.numMessages - getn( SendMail_state.attachments ), + SendMail_state.numMessages ) + end + + api.SendMail( SendMail_state.to, subject, SendMail_state.body ) + + if getn( SendMail_state.attachments ) == 0 then + SendMail_sending = false + end +end + +do + local inputLength + local matches = {} + local index + + local function complete() + api.SendMailNameEditBox:SetText( matches[ index ] ) + api.SendMailNameEditBox:HighlightText( inputLength, -1 ) + for i = 1, api.MAIL_AUTOCOMPLETE_MAX_BUTTONS do + local button = api[ "MailAutoCompleteButton" .. i ] + if i == index then + button:LockHighlight() + else + button:UnlockHighlight() + end + end + end + + function PreviousMatch() + if index then + index = index > 1 and index - 1 or getn( matches ) + complete() + end + end + + function NextMatch() + if index then + ---@diagnostic disable-next-line: undefined-global + index = mod( index, getn( matches ) ) + 1 + complete() + end + end + + function m.SelectMatch( i ) + index = i + complete() + api.MailAutoCompleteBox:Hide() + api.SendMailNameEditBox:HighlightText( 0, 0 ) + end + + function GetSuggestions() + local input = api.SendMailNameEditBox:GetText() + inputLength = string.len( input ) + + ---@diagnostic disable-next-line: undefined-field + table.setn( matches, 0 ) + index = nil + + local autoCompleteNames = {} + for name, time in api.TurtleMail_AutoCompleteNames[ api.GetCVar "realmName" .. "|" .. api.UnitFactionGroup "player" ] do + table.insert( autoCompleteNames, { name = name, time = time } ) + end + table.sort( autoCompleteNames, function( a, b ) return b.time < a.time end ) + + local ignore = { [ api.UnitName "player" ] = true } + local function process( name ) + if name then + if not ignore[ name ] and string.find( string.upper( name ), string.upper( input ), nil, true ) == 1 then + table.insert( matches, name ) + end + ignore[ name ] = true + end + end + for _, t in autoCompleteNames do + process( t.name ) + end + for i = 1, api.GetNumFriends() do + process( api.GetFriendInfo( i ) ) + end + for i = 1, api.GetNumGuildMembers( true ) do + process( api.GetGuildRosterInfo( i ) ) + end + + ---@diagnostic disable-next-line: undefined-field + table.setn( matches, math.min( getn( matches ), api.MAIL_AUTOCOMPLETE_MAX_BUTTONS ) ) + if getn( matches ) > 0 and (getn( matches ) > 1 or input ~= matches[ 1 ]) then + for i = 1, api.MAIL_AUTOCOMPLETE_MAX_BUTTONS do + local button = api[ "MailAutoCompleteButton" .. i ] + if i <= getn( matches ) then + button:SetText( matches[ i ] ) + button:GetFontString():SetPoint( "LEFT", button, "LEFT", 15, 0 ) + button:Show() + else + button:Hide() + end + end + api.MailAutoCompleteBox:SetHeight( getn( matches ) * api.MailAutoCompleteButton1:GetHeight() + 35 ) + api.MailAutoCompleteBox:SetWidth( 120 ) + api.MailAutoCompleteBox:Show() + index = 1 + complete() + else + api.MailAutoCompleteBox:Hide() + end + end +end + +-- ************ +-- Localization +do + local function defaultFunc( _, key ) + return key; + end + setmetatable( L, { __index = defaultFunc } ); + + L[ "collected" ] = nil + L[ "1st mail" ] = nil + L[ "each mail" ] = nil + L[ "Money received" ] = nil + + if api.GetLocale() == "frFR" then + L[ "collected" ] = "collecté" + L[ "1st mail" ] = "1er courrier" + L[ "each mail" ] = "chaque courrier" + L[ "Money received" ] = "Argent reçu" + end + + if api.GetLocale() == "esES" then + L[ "collected" ] = "recogido" + L[ "1st mail" ] = "1er correo" + L[ "each mail" ] = "cada correo" + L[ "Money received" ] = "Dinero recibido" + end + + if api.GetLocale() == "deDE" then + L[ "collected" ] = "gesammelt" + L[ "1st mail" ] = "1. Mail" + L[ "each mail" ] = "jede Mail" + L[ "Money received" ] = "Geld erhalten" + end + + if api.GetLocale() == "ruRU" then + L[ "collected" ] = "собрано" + L[ "1st mail" ] = "1-я почта" + L[ "each mail" ] = "каждая почта" + L[ "Money received" ] = "Деньги получены" + end +end diff --git a/TurtleMail.toc b/TurtleMail.toc new file mode 100644 index 0000000..bc80581 --- /dev/null +++ b/TurtleMail.toc @@ -0,0 +1,10 @@ + +## Interface: 11200 +## Title: |cffabd473Turtle|cffffffffMail +## Author: shirsig/sica +## Version: 1.0 +## Notes: Mailbox enhancement +## SavedVariables: TurtleMail_AutoCompleteNames +## SavedVariablesPerCharacter: TurtleMail_To TurtleMail_Point + +TurtleMail.xml diff --git a/Mail.xml b/TurtleMail.xml similarity index 86% rename from Mail.xml rename to TurtleMail.xml index 9d4ed3b..e100b45 100644 --- a/Mail.xml +++ b/TurtleMail.xml @@ -1,6 +1,6 @@ -