Browse Source

init

pull/2/head
Andrew6810 4 years ago
parent
commit
f3e579cb57
  1. 4
      README.md
  2. 289
      TradeSkillMaster/Assistant/Assistant.lua
  3. 191
      TradeSkillMaster/Assistant/Questions.lua
  4. 629
      TradeSkillMaster/Assistant/Steps.lua
  5. 824
      TradeSkillMaster/Auction/AuctionControl.lua
  6. 298
      TradeSkillMaster/Auction/AuctionFrame.lua
  7. 433
      TradeSkillMaster/Auction/AuctionItem.lua
  8. 314
      TradeSkillMaster/Auction/AuctionQueryUtil.lua
  9. 730
      TradeSkillMaster/Auction/AuctionResultsTable.lua
  10. 628
      TradeSkillMaster/Auction/AuctionScanning.lua
  11. 116
      TradeSkillMaster/Auction/AuctionUtil.lua
  12. 442
      TradeSkillMaster/ChangeLog.txt
  13. 415
      TradeSkillMaster/Core/ErrorHandler.lua
  14. 49
      TradeSkillMaster/Core/EventLogger.lua
  15. 62
      TradeSkillMaster/Core/Events.lua
  16. 1687
      TradeSkillMaster/Core/Groups.lua
  17. 368
      TradeSkillMaster/Core/Modules.lua
  18. 699
      TradeSkillMaster/Core/Mover.lua
  19. 1183
      TradeSkillMaster/Core/Options.lua
  20. 458
      TradeSkillMaster/Core/Prices.lua
  21. 205
      TradeSkillMaster/Core/Sync.lua
  22. 21
      TradeSkillMaster/Core/Templates.xml
  23. 113
      TradeSkillMaster/Core/Threading.lua
  24. 300
      TradeSkillMaster/Core/Tooltips.lua
  25. 134
      TradeSkillMaster/Data/ConnectedRealms.lua
  26. 717
      TradeSkillMaster/Data/Conversions.lua
  27. 1636
      TradeSkillMaster/Data/Disenchanting.lua
  28. 32
      TradeSkillMaster/Data/ItemData.lua
  29. 104
      TradeSkillMaster/Data/Vendor.lua
  30. 346
      TradeSkillMaster/GUI/BankUI.lua
  31. 433
      TradeSkillMaster/GUI/BuildPage.lua
  32. 128
      TradeSkillMaster/GUI/Design.lua
  33. 373
      TradeSkillMaster/GUI/GroupTree.lua
  34. 338
      TradeSkillMaster/GUI/Gui.lua
  35. 140
      TradeSkillMaster/GUI/MainFrame.lua
  36. 390
      TradeSkillMaster/GUI/ScrollingTable.lua
  37. 120
      TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua
  38. 181
      TradeSkillMaster/GUI/TSMWidgets/TSMCheckBox.lua
  39. 187
      TradeSkillMaster/GUI/TSMWidgets/TSMColorPicker.lua
  40. 258
      TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Items.lua
  41. 315
      TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Pullout.lua
  42. 413
      TradeSkillMaster/GUI/TSMWidgets/TSMDropdown.lua
  43. 283
      TradeSkillMaster/GUI/TSMWidgets/TSMEditBox.lua
  44. 231
      TradeSkillMaster/GUI/TSMWidgets/TSMGroupBox.lua
  45. 515
      TradeSkillMaster/GUI/TSMWidgets/TSMGroupItemList.lua
  46. 71
      TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua
  47. 68
      TradeSkillMaster/GUI/TSMWidgets/TSMInlineGroup.lua
  48. 113
      TradeSkillMaster/GUI/TSMWidgets/TSMInteractiveLabel.lua
  49. 107
      TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua
  50. 388
      TradeSkillMaster/GUI/TSMWidgets/TSMMainFrame.lua
  51. 85
      TradeSkillMaster/GUI/TSMWidgets/TSMMultiLabel.lua
  52. 21
      TradeSkillMaster/GUI/TSMWidgets/TSMMultiLineEditBox.lua
  53. 223
      TradeSkillMaster/GUI/TSMWidgets/TSMScrollFrame.lua
  54. 21
      TradeSkillMaster/GUI/TSMWidgets/TSMSimpleGroup.lua
  55. 279
      TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua
  56. 312
      TradeSkillMaster/GUI/TSMWidgets/TSMTabGroup.lua
  57. 735
      TradeSkillMaster/GUI/TSMWidgets/TSMTreeGroup.lua
  58. 169
      TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua
  59. 186
      TradeSkillMaster/Items.lua
  60. 21
      TradeSkillMaster/LICENSE.txt
  61. 12
      TradeSkillMaster/Libs/AccurateTime/!AccurateTime.toc
  62. 153
      TradeSkillMaster/Libs/AccurateTime/AccurateTime.lua
  63. 674
      TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.lua
  64. 4
      TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.xml
  65. 308
      TradeSkillMaster/Libs/AceComm-3.0/AceComm-3.0.lua
  66. 5
      TradeSkillMaster/Libs/AceComm-3.0/AceComm-3.0.xml
  67. 523
      TradeSkillMaster/Libs/AceComm-3.0/ChatThrottleLib.lua
  68. 58
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfig-3.0.lua
  69. 8
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfig-3.0.xml
  70. 794
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua
  71. 4
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml
  72. 1962
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua
  73. 4
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml
  74. 371
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua
  75. 4
      TradeSkillMaster/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml
  76. 250
      TradeSkillMaster/Libs/AceConsole-3.0/AceConsole-3.0.lua
  77. 4
      TradeSkillMaster/Libs/AceConsole-3.0/AceConsole-3.0.xml
  78. 741
      TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.lua
  79. 4
      TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.xml
  80. 460
      TradeSkillMaster/Libs/AceDBOptions-3.0/AceDBOptions-3.0.lua
  81. 4
      TradeSkillMaster/Libs/AceDBOptions-3.0/AceDBOptions-3.0.xml
  82. 126
      TradeSkillMaster/Libs/AceEvent-3.0/AceEvent-3.0.lua
  83. 4
      TradeSkillMaster/Libs/AceEvent-3.0/AceEvent-3.0.xml
  84. 1031
      TradeSkillMaster/Libs/AceGUI-3.0/AceGUI-3.0.lua
  85. 29
      TradeSkillMaster/Libs/AceGUI-3.0/AceGUI-3.0.xml
  86. 138
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua
  87. 157
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-DropDownGroup.lua
  88. 316
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua
  89. 103
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua
  90. 215
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua
  91. 69
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-SimpleGroup.lua
  92. 349
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua
  93. 705
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua
  94. 336
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua
  95. 179
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button-ElvUI.lua
  96. 103
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua
  97. 295
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua
  98. 205
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua
  99. 471
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua
  100. 771
      TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua
  101. Some files were not shown because too many files have changed in this diff Show More

4
README.md

@ -1,3 +1,3 @@
# Addon Name # TradeSkillMaster
This is the repository for <Addon Name>. Modified for Ascension.gg. This is the repository for Trade Skill Master (TSM). Modified for Ascension.gg.

289
TradeSkillMaster/Assistant/Assistant.lua

@ -0,0 +1,289 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local Assistant = TSM:NewModule("Assistant")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Assistant_private")
local MAX_ASSISTANT_BUTTONS = 6
function Assistant:Open()
if not private.frame then
if not private:ValidateQuestions(Assistant.INFO) then
TSM:Print(L["No Assistant guides available for the modules which you have installed."])
return
end
private.frame = private:CreateAssistantFrame()
end
private.frame:Show()
end
-- Removes questions which aren't possible due to missing steps (probably due to missing modules)
function private:ValidateQuestions(questionInfo)
if not questionInfo.buttons then return false end
for i=#questionInfo.buttons, 1, -1 do
if questionInfo.buttons[i].guides then
local hasAllGuides = true
for _, guide in ipairs(questionInfo.buttons[i].guides) do
if not Assistant.STEPS[guide] then
hasAllGuides = false
break
end
end
if not hasAllGuides then
tremove(questionInfo.buttons, i)
end
elseif questionInfo.buttons[i].children then
if not private:ValidateQuestions(questionInfo.buttons[i].children) then
tremove(questionInfo.buttons, i)
end
end
end
return #questionInfo.buttons > 0
end
function private:CreateAssistantFrame()
local frameDefaults = {
x = 50,
y = 300,
width = 400,
height = 250,
scale = 1,
}
local frame = TSMAPI:CreateMovableFrame("TSMAssistantFrame", frameDefaults)
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:Hide()
frame:SetScript("OnShow", function(self)
self.guideFrame:Hide()
self.questionFrame:Show()
end)
frame:SetScript("OnHide", function(self)
private.currentStep = nil
end)
local title = frame:CreateFontString()
title:SetFont(TSMAPI.Design:GetContentFont(), 18)
TSMAPI.Design:SetWidgetLabelColor(title)
title:SetPoint("TOP", frame, 0, -3)
title:SetText(L["TSM Assistant"])
TSMAPI.GUI:CreateHorizontalLine(frame, -25)
local closeBtn = TSMAPI.GUI:CreateButton(frame, 18)
closeBtn:SetPoint("TOPRIGHT", -3, -3)
closeBtn:SetWidth(19)
closeBtn:SetHeight(19)
closeBtn:SetText("X")
closeBtn:SetScript("OnClick", function() frame:Hide() end)
frame.questionFrame = private:CreateQuestionFrame(frame)
frame.guideFrame = private:CreateGuideFrame(frame)
return frame
end
function private:CreateQuestionFrame(parent)
local frame = CreateFrame("Frame", nil, parent)
frame:SetAllPoints()
frame:Hide()
frame:SetScript("OnShow", function(self)
private.pageInfo = Assistant.INFO
self.restartButton:Hide()
self:Update()
end)
function frame.Update(self)
-- update the title question
self.questionText:SetText(private.pageInfo.title)
-- hide all the buttons
for _, button in ipairs(self.buttons) do
button:Hide()
end
-- update buttons
for i, buttonInfo in ipairs(private.pageInfo.buttons) do
self.buttons[i]:Show()
self.buttons[i]:SetText(buttonInfo.text)
self.buttons[i].info = buttonInfo
end
end
local questionText = TSMAPI.GUI:CreateTitleLabel(frame, 16)
questionText:SetPoint("TOPLEFT", 5, -30)
questionText:SetPoint("TOPRIGHT", -5, -30)
questionText:SetHeight(20)
questionText:SetJustifyH("LEFT")
questionText:SetJustifyV("CENTER")
frame.questionText = questionText
local function OnAnswerButtonClicked(self)
if self.info.children then
private.frame.questionFrame.restartButton:Show()
private.pageInfo = self.info.children
private.frame.questionFrame:Update()
elseif self.info.guides then
private.steps = {}
for _, guideKey in ipairs(self.info.guides) do
for _, step in ipairs(Assistant.STEPS[guideKey]) do
tinsert(private.steps, step)
end
end
private.frame.questionFrame:Hide()
private.frame.guideFrame:Show()
end
end
frame.buttons = {}
for i=1, MAX_ASSISTANT_BUTTONS do
local button = TSMAPI.GUI:CreateButton(frame, 14)
button:SetHeight(20)
if i == 1 then
button:SetPoint("TOPLEFT", frame.questionText, "BOTTOMLEFT", 0, -5)
button:SetPoint("TOPRIGHT", frame.questionText, "BOTTOMRIGHT", 0, -5)
else
button:SetPoint("TOPLEFT", frame.buttons[i-1], "BOTTOMLEFT", 0, -5)
button:SetPoint("TOPRIGHT", frame.buttons[i-1], "BOTTOMRIGHT", 0, -5)
end
button:SetScript("OnClick", OnAnswerButtonClicked)
tinsert(frame.buttons, button)
end
local restartButton = TSMAPI.GUI:CreateButton(frame, 14)
restartButton:SetHeight(20)
restartButton:SetPoint("BOTTOMLEFT", 5, 5)
restartButton:SetPoint("BOTTOMRIGHT", -5, 5)
restartButton:SetText(L["Restart Assistant"])
restartButton:SetScript("OnClick", function(self)
self:Hide()
private.pageInfo = Assistant.INFO
private.frame.questionFrame:Update()
end)
frame.restartButton = restartButton
return frame
end
function private:CreateGuideFrame(parent)
local frame = CreateFrame("Frame", nil, parent)
frame:SetAllPoints()
frame:Hide()
frame:SetScript("OnShow", function(self)
private.currentStep = 1
private.checkPoint = 1
Assistant:ClearStepData()
self:Update()
private:StartStepWaitThread()
end)
function frame.Update(self)
if private.currentStep == -1 then
self.stepTitle:SetText(L["Done!"])
self.stepDesc:SetText(L["You have successfully completed this guide. If you require further assistance, visit out our website:"].."\n\n".."http://tradeskillmaster.com")
self.button:Hide()
self.restartButton:Show()
else
local stepInfo = private.steps[private.currentStep]
self.stepTitle:SetText(stepInfo.title)
if stepInfo.getDescArgs then
self.stepDesc:SetText(format(stepInfo.description, stepInfo.getDescArgs()))
else
self.stepDesc:SetText(stepInfo.description)
end
if stepInfo.doneButton then
self.button:Show()
self.button:SetText(stepInfo.doneButton)
self.button:SetScript("OnClick", function() stepInfo:onDoneButtonClicked() end)
self.stepDesc:SetWidth(min(self.stepDesc:GetStringWidth(), self:GetWidth()-10))
else
self.button:Hide()
self.stepDesc:SetWidth(self:GetWidth()-10)
end
self.restartButton:Hide()
end
end
local stepTitle = TSMAPI.GUI:CreateTitleLabel(frame, 16)
stepTitle:SetPoint("TOPLEFT", 5, -30)
stepTitle:SetPoint("TOPRIGHT", -5, -30)
stepTitle:SetHeight(20)
stepTitle:SetJustifyH("LEFT")
stepTitle:SetJustifyV("CENTER")
stepTitle:SetText("DEFAULT")
frame.stepTitle = stepTitle
local stepDesc = TSMAPI.GUI:CreateLabel(frame, "normal")
stepDesc:SetPoint("TOPLEFT", 5, -55)
stepDesc:SetJustifyH("LEFT")
stepDesc:SetJustifyV("TOP")
frame.stepDesc = stepDesc
local button = TSMAPI.GUI:CreateButton(frame, 14)
button:SetHeight(20)
button:SetPoint("TOPLEFT", frame.stepDesc, "BOTTOMLEFT", 0, -5)
button:SetPoint("TOPRIGHT", frame.stepDesc, "BOTTOMRIGHT", 0, -5)
button:SetScript("OnClick", function() end)
frame.button = button
local restartButton = TSMAPI.GUI:CreateButton(frame, 14)
restartButton:SetHeight(20)
restartButton:SetPoint("BOTTOMLEFT", 5, 5)
restartButton:SetPoint("BOTTOMRIGHT", -5, 5)
restartButton:SetText(L["Restart Assistant"])
restartButton:SetScript("OnClick", function(self)
parent:Hide()
Assistant:Open()
end)
frame.restartButton = restartButton
return frame
end
function private:StartStepWaitThread()
TSMAPI.Threading:Start(private.GuideThread, 0.1, private.StepComplete)
end
function private:IsStepDone(step)
if step.isDone and step:isDone() then
return true
end
end
function private:GetCurrentStep()
for i=private.checkPoint, #private.steps do
if not private:IsStepDone(private.steps[i]) then
return i
elseif private.steps[i].isCheckPoint then
private.checkPoint = i+1
end
end
end
function private.GuideThread(self)
-- loop until the player finishes the step or we abort
while private.currentStep do
local stepNum = private:GetCurrentStep()
if not stepNum then return end
if stepNum ~= private.currentStep then
private.currentStep = stepNum
end
private.frame.guideFrame:Update()
self:Sleep(0.1)
end
end
function private.StepComplete()
if private.currentStep then
private.currentStep = -1
private.frame.guideFrame:Update()
end
end

191
TradeSkillMaster/Assistant/Questions.lua

@ -0,0 +1,191 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local Assistant = TSM.modules.Assistant
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Questions_private")
local MAKE_GROUP_STEPS = {
title = L["How would you like to create the group?"],
buttons = {
{
text = L["Make a new group from items in my bags"],
guides = {"openTSM", "openGroups", "newGroup", "selectGroup", "groupAddFromBags"},
},
{
text = L["Make a new group from an import list I have"],
guides = {"openTSM", "openGroups", "newGroup", "selectGroup", "groupImportItems"},
},
{
text = L["Use an existing group"],
guides = {"openTSM", "openGroups", "selectGroup"},
},
{
text = L["Use a subset of items from an existing group by creating a subgroup"],
guides = {"openTSM", "openGroups", "selectGroup"},
},
},
}
function private:GetMakeGroupSteps(steps)
local stepInfo = CopyTable(MAKE_GROUP_STEPS)
for _, button in ipairs(stepInfo.buttons) do
for _, step in ipairs(steps) do
tinsert(button.guides, step)
end
end
return stepInfo
end
Assistant.INFO = {
title = L["What do you want to do?"],
buttons = {
{
text = L["Craft items with my professions"],
children = {
title = L["How would you like to craft?"],
buttons = {
{
text = L["Set up TSM to automatically queue things to craft"],
children = private:GetMakeGroupSteps({"craftingOperation", "openProfession", "professionRestock", "useProfessionQueue"}),
},
{
text = L["Look at what's profitable to craft and manually add things to a queue"],
guides = {"openTSM", "openCrafting", "craftingCraftsTab", "openProfession", "useProfessionQueue"},
},
{
text = L["Craft specific one-off items without making a queue"],
guides = {"openProfession", "craftFromProfession"},
},
},
},
},
{
text = L["Buy items from the AH"],
children = {
title = L["How would you like to shop?"],
buttons = {
{
text = L["Set up TSM to find cheap items on the AH"],
children = private:GetMakeGroupSteps({"shoppingOperation", "openShoppingAHTab", "shoppingGroupSearch", "shoppingWaitForScan"}),
},
{
text = L["Search the AH for items to buy"],
guides = {"openShoppingAHTab", "shoppingFilterSearch", "shoppingWaitForScan"},
},
-- {
-- text = L["Buy materials for my TSM_Crafting queue"],
-- guides = {"notYetImplemented"},
-- },
{
text = L["Advanced topics..."],
children = {
title = L["How would you like to shop?"],
buttons = {
{
text = L["Snipe items as they are being posted to the AH"],
guides = {"openShoppingAHTab", "shoppingOtherSidebar", "shoppingSniperSearch"},
},
{
text = L["Look for items which can be destroyed to get raw mats"],
guides = {"openShoppingAHTab", "shoppingDestroySearch", "shoppingWaitForScan"},
},
{
text = L["Look for items which can be vendored for a profit"],
guides = {"openShoppingAHTab", "shoppingOtherSidebar", "shoppingVendorSearch", "shoppingWaitForScan"},
},
-- {
-- text = L["Setup TSM to automatically reset specific markets"],
-- children = private:GetMakeGroupSteps({"auctioningOperationReset", "openAuctioningAHTab", "notYetImplemented", "auctioningWaitForScan"}),
-- },
},
},
},
},
},
},
{
text = L["Sell items on the AH and manage my auctions"],
children = {
title = L["What do you want to do?"],
buttons = {
{
text = L["Set up TSM to automatically post auctions"],
children = private:GetMakeGroupSteps({"auctioningOperationPost", "openAuctioningAHTab", "auctioningPostScan", "auctioningWaitForScan"}),
},
{
text = L["Set up TSM to automatically cancel undercut auctions"],
children = private:GetMakeGroupSteps({"auctioningOperationCancel", "openAuctioningAHTab", "auctioningCancelScan", "auctioningWaitForScan"}),
},
{
text = L["Post items manually from my bags"],
children = {
title = L["How would you like to post?"],
buttons = {
{
text = L["View current auctions and choose what price to post at"],
guides = {"openShoppingAHTab", "shoppingSearchFromBags", "shoppingWaitForScanSilent", "shoppingPostFromResults"},
},
{
text = L["Quickly post my items at some pre-determined price"],
guides = {"openTSM", "openShoppingOptions", "shoppingQuickPostingSettings", "openShoppingAHTab", "shoppingQuickPosting"},
},
},
},
},
},
},
},
-- {
-- text = "Mail items to another character",
-- children = {
-- title = "How would you like to mail items?",
-- buttons = {
-- {
-- text = "Setup TSM to mail items automatically",
-- guides = {"notYetImplemented"},
-- },
-- {
-- text = "Quickly send a specific item to another player (with or without COD)",
-- guides = {"notYetImplemented"},
-- },
-- {
-- text = "Mail disenchantable items to another character",
-- guides = {"notYetImplemented"},
-- },
-- {
-- text = "Send excess gold to another character",
-- guides = {"notYetImplemented"},
-- },
-- },
-- },
-- },
-- {
-- text = "Move items between my bags, bank, and guild bank",
-- children = {
-- title = "How would you like to move items?",
-- buttons = {
-- {
-- text = "Setup TSM to move items automatically",
-- guides = {"notYetImplemented"},
-- },
-- {
-- text = "Move a few items quickly between my bags and bank or guild bank",
-- guides = {"notYetImplemented"},
-- },
-- {
-- text = "Get items out of the bank or guild bank to post on the AH",
-- guides = {"notYetImplemented"},
-- },
-- },
-- },
-- },
},
}

629
TradeSkillMaster/Assistant/Steps.lua

@ -0,0 +1,629 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local Assistant = TSM.modules.Assistant
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local private = {stepData = {recentEvents = {}}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Steps_private")
local eventObj = TSMAPI:GetEventObject()
function Assistant:ClearStepData()
private.stepData = {recentEvents={}}
end
function private.OnEvent(event, arg)
if not private.stepData then return end
local order = (private.stepData.recentEvents._order or 0) + 1
private.stepData.recentEvents[event] = {arg=arg, order=order}
private.stepData.recentEvents._order = order
end
eventObj:SetCallbackAnyEvent(private.OnEvent)
function private:IsTSMFrameIconSelected(iconText)
local path = TSM:GetTSMFrameSelectionPath()
return path and path[1].value == iconText
end
function private:GetPathLevelValue(iconText, level)
local path = TSM:GetTSMFrameSelectionPath()
return path and path[1] and path[1].value == iconText and path[level] and path[level].value
end
function private:GetGroupTreeSelection()
return private:GetPathLevelValue(L["Groups"], 2)
end
function private:GetGroupTab()
local temp = private:GetPathLevelValue(L["Groups"], 2)
return temp and temp[#temp] == private.stepData.selectedGroup and private:GetPathLevelValue(L["Groups"], 3)
end
function private:GetOperationModuleSelection()
return private:GetPathLevelValue(L["Module Operations / Options"], 2)
end
function private:GetOperationTreeSelection(module)
if private:GetOperationModuleSelection() ~= module then return end
return private:GetPathLevelValue(L["Module Operations / Options"], 3)
end
function private:GetOperationTab(module)
if not private:GetOperationTreeSelection(module) then return end
local temp = private:GetPathLevelValue(L["Module Operations / Options"], 3)
return temp and temp and temp[#temp] == private.stepData.selectedOperation and private:GetPathLevelValue(L["Module Operations / Options"], 4)
end
function private:GetIsDoneStep(title, description, isDoneFunc)
local step = {
title = title,
description = description,
doneButton = L["I'm done."],
isDone = function(self) return private.stepData[self] and (not isDoneFunc or isDoneFunc()) end,
onDoneButtonClicked = function(self) private.stepData[self] = true end,
isCheckPoint = true
}
return step
end
function private:GetEventIsDone(targetEvent)
local function isDone(self)
if private.stepData[self] then return true end
if not private.stepData.recentEvents[targetEvent] then return end
wipe(private.stepData.recentEvents)
private.stepData[self] = true
return true
end
return isDone
end
function private:PrependCreateOperationSteps(tbl, moduleLong, moduleShort, description, operationsIndex)
local steps = {
{
title = L["Go to the 'Operations' Tab"],
description = format(L["We will add a %s operation to this group through its 'Operations' tab. Click on that tab now."], moduleLong),
isDone = function() return private:GetGroupTab() == 1 end,
isCheckPoint = true,
},
{
title = format(L["Create a %s Operation %d/5"], moduleShort, 1),
description = description,
isDone = function() return private:GetOperationTreeSelection(moduleShort) end,
},
{
title = format(L["Create a %s Operation %d/5"], moduleShort, 2),
description = L["Select the 'Operations' page from the list on the left of the TSM window."],
isDone = function()
local selection = private:GetOperationTreeSelection(moduleShort)
if selection and #selection == 1 and selection[1] == tostring(operationsIndex) then
private.stepData.operationsPageClicked = true
end
return private.stepData.operationsPageClicked
end,
},
{
title = format(L["Create a %s Operation %d/5"], moduleShort, 3),
description = format(L["Create a new %s operation by typing a name for the operation into the 'Operation Name' box and pressing the <Enter> key."], moduleLong),
isDone = function()
local selection = private:GetOperationTreeSelection(moduleShort)
return selection and #selection > 1 and selection[1] == tostring(operationsIndex)
end,
},
{
title = format(L["Create a %s Operation %d/5"], moduleShort, 4),
description = L["Assign this operation to the group you previously created by clicking on the 'Yes' button in the popup that's now being shown."],
isDone = function()
for i=1, 100 do
local popup = _G["StaticPopup"..i]
if not popup then break end
if popup:IsVisible() and popup.which == "TSM_NEW_OPERATION_ADD" then
return
end
end
return true
end,
},
{
title = format(L["Create a %s Operation %d/5"], moduleShort, 5),
description = L["Select your new operation in the list of operation along the left of the TSM window (if it's not selected automatically) and click on the button below.\n\nCurrently Selected Operation: %s"],
getDescArgs = function()
local selection = private:GetOperationTreeSelection(moduleShort)
if selection and #selection > 1 then
return TSMAPI.Design:GetInlineColor("link")..selection[#selection].."|r"
else
return TSMAPI.Design:GetInlineColor("link")..L["<No Operation Selected>"].."|r"
end
end,
isDone = function() return private:GetOperationTab(moduleShort) end,
doneButton = L["My new operation is selected."],
onDoneButtonClicked = function()
local selection = private:GetOperationTreeSelection(moduleShort)
if selection and #selection > 1 then
private.stepData.selectedOperation = selection[#selection]
else
TSM:Print(L["Please select the new operation you've created."])
end
end,
isCheckPoint = true,
},
}
-- prepend all the steps to the passed table
for i, step in ipairs(steps) do
tinsert(tbl, i, step)
end
end
local tsmSteps = {
["notYetImplemented"] = {
private:GetIsDoneStep(
"Not Yet Implemented",
"This step is not yet implemented.\n\nTHIS SHOULD NEVER BE SEEN IN A RELEASE VERSION OF TSM!"
),
},
["openTSM"] = {
{
title = L["Open the TSM Window"],
description = L["Type '/tsm' or click on the minimap icon to open the main TSM window."],
isDone = function() return TSM:TSMFrameIsVisible() end,
},
},
["openGroups"] = {
{
title = L["Click on the Groups Icon"],
description = L["Along top of the window, on the left side, click on the 'Groups' icon to open up the TSM group settings."],
isDone = function() return private:IsTSMFrameIconSelected(L["Groups"]) end,
},
},
["newGroup"] = {
{
title = L["Go to the 'Groups' Page"],
description = L["In the list on the left, select the top-level 'Groups' page."],
isDone = function()
local selection = private:GetGroupTreeSelection()
if selection and #selection == 1 and selection[1] == "1" then
private.stepData.groupsPageClicked = true
end
return private.stepData.groupsPageClicked
end,
},
{
title = L["Create a New Group"],
description = L["Create a new group by typing a name for the group into the 'Group Name' box and pressing the <Enter> key."],
isDone = private:GetEventIsDone("TSM:GROUPS:NEWGROUP"),
},
},
["selectGroup"] = {
{
title = L["Select Existing Group"],
description = L["Select the group you'd like to use. Once you have done this, click on the button below.\n\nCurrently Selected Group: %s"],
getDescArgs = function()
local selection = private:GetGroupTreeSelection()
if selection and #selection > 1 then
return TSMAPI:FormatGroupPath(selection[#selection], true)
else
return TSMAPI.Design:GetInlineColor("link")..L["<No Group Selected>"].."|r"
end
end,
isDone = function() return private:GetGroupTab() end,
doneButton = L["My group is selected."],
onDoneButtonClicked = function()
local selection = private:GetGroupTreeSelection()
if selection and #selection > 1 then
private.stepData.selectedGroup = selection[#selection]
else
TSM:Print(L["Please select the group you'd like to use."])
end
end,
},
},
["groupImportItems"] = {
{
title = L["Go to the 'Import/Export' Tab"],
description = L["We will import items into this group using the import list you have."],
isDone = function() return private:GetGroupTab() == 3 or private.stepData.importedItems end,
},
{
title = L["Enter Import String"],
description = L["Paste your import string into the 'Import String' box and hit the <Enter> key to import the list of items."],
isDone = function()
if private.stepData.importedItems then return true end
if not private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"] then return end
local arg = CopyTable(private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"].arg)
wipe(private.stepData.recentEvents)
if arg.isImport then
if arg.num == 0 then
TSM:Print(L["Looks like no items were imported. This might be because they are already in another group in which case you might consider checking the 'Move Already Grouped Items' box to force them to move to this group."])
else
private.stepData.importedItems = true
return true
end
end
end,
},
},
["groupAddFromBags"] = {
{
title = L["Go to the 'Items' Tab"],
description = L["We will add items to this group through its 'Items' tab. Click on that tab now."],
isDone = function() return private:GetGroupTab() == 2 or private.stepData.addedItems end,
},
{
title = L["Add Items to this Group"],
description = L["Select the items you want to add in the left column and then click on the 'Add >>>' button at the top to add them to this group."],
isDone = function()
if private.stepData.addedItems then return true end
if not private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"] then return end
local arg = CopyTable(private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"].arg)
wipe(private.stepData.recentEvents)
if not arg.isImport then
private.stepData.addedItems = true
return true
end
end,
},
},
}
local craftingSteps = {
["openCrafting"] = {
{
title = L["Click on the Crafting Icon"],
description = L["Along top of the window, on the right side, click on the 'Crafting' icon to open up the TSM_Crafting page."],
isDone = function() return private:GetPathLevelValue("Crafting", 2) == 1 end,
},
},
["craftingCraftsTab"] = {
{
title = L["Select the 'Crafts' Tab"],
description = L["At the top, switch to the 'Crafts' tab in order to view a list of crafts you can make."],
isDone = function() return TSM:TSMFrameIsVisible() end,
},
private:GetIsDoneStep(
L["Queue Profitable Crafts"],
L["You can use the filters at the top of the page to narrow down your search and click on a column to sort by that column. Then, left-click on a row to add one of that item to the queue, and right-click to remove one.\n\nOnce you're done adding items to the queue, click the button below."]
),
},
["openProfession"] = {
{
title = L["Open up Your Profession"],
description = L["Open one of the professions which you would like to use to craft items."],
isDone = function() return TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") end,
},
},
["useProfessionQueue"] = {
{
title = L["Show the Queue"],
description = L["Click on the 'Show Queue' button at the top of the TSM_Crafting window to show the queue if it's not already visible."],
isDone = function() local status = TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") return status and status.queue end,
},
private:GetIsDoneStep(
L["Craft Items from Queue"],
L["You can craft items either by clicking on rows in the queue which are green (meaning you can craft all) or blue (meaning you can craft some) or by clicking on the 'Craft Next' button at the bottom.\n\nClick on the button below when you're done reading this. There is another guide which tells you how to buy mats required for your queue."]
),
},
["craftingOperation"] = {
{
title = L["Select the 'General' Tab"],
description = "Select the 'General' tab within the operation to set the general options for the TSM_Crafting operation.",
isDone = function() return private:GetOperationTab("Crafting") == 1 end
},
private:GetIsDoneStep(
L["Set Max Restock Quantity"],
L["The 'Max Restock Quantity' defines how many of each item you want to restock up to when using the restock queue, taking your inventory into account.\n\nOnce you're done adjusting this setting, click the button below."]
),
private:GetIsDoneStep(
L["Set Minimum Profit"],
L["If you'd like, you can adjust the value in the 'Minimum Profit' box in order to specify the minimum profit before Crafting will queue these items.\n\nOnce you're done adjusting this setting, click the button below."]
),
private:GetIsDoneStep(
L["Set Other Options"],
L["You can look through the tooltips of the other options to see what they do and decide if you want to change their values for this operation.\n\nOnce you're done, click on the button below."]
),
},
["professionRestock"] = {
{
title = L["Switch to the 'TSM Groups' Tab"],
description = L["Along the top of the TSM_Crafting window, click on the 'TSM Groups' button."],
isDone = function() local status = TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") return status and status.page == "groups" end,
},
{
title = L["Select Group and Click Restock Button"],
description = L["First, ensure your new group is selected in the group-tree and then click on the 'Restock Selected Groups' button at the bottom."],
isDone = function(self)
if private.stepData[self] then return true end
if not private.stepData.recentEvents["CRAFTING:QUEUE:RESTOCKED"] then return end
local arg = private.stepData.recentEvents["CRAFTING:QUEUE:RESTOCKED"].arg
wipe(private.stepData.recentEvents)
if arg == 0 then
TSM:Print(L["Looks like no items were added to the queue. This may be because you are already at or above your restock levels, or there is nothing profitable to queue."])
else
private.stepData[self] = true
return true
end
end,
},
},
["craftFromProfession"] = {
{
title = L["Switch to the 'Professions' Tab"],
description = L["Along the top of the TSM_Crafting window, click on the 'Professions' button."],
isDone = function() local status = TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") return status and status.page == "profession" end,
},
private:GetIsDoneStep(
L["Select the Craft"],
L["Just like the default profession UI, you can select what you want to craft from the list of crafts for this profession. Click on the one you want to craft.\n\nOnce you're done, click the button below."]
),
private:GetIsDoneStep(
L["Create the Craft"],
L["You can now use the buttons near the bottom of the TSM_Crafting window to create this craft.\n\nOnce you're done, click the button below."]
),
},
}
private:PrependCreateOperationSteps(craftingSteps["craftingOperation"], "TSM_Crafting", "Crafting", L["A TSM_Crafting operation will allow you automatically queue profitable items from the group you just made. To create one for this group, scroll down to the 'Crafting' section, and click on the 'Create Crafting Operation' button."], 2)
local auctioningSteps = {
["auctioningOperationPost"] = {
{
title = L["Select the Post Tab"],
description = L["Select the 'Post' tab within the operation to set the posting options for the TSM_Auctioning operation."],
isDone = function() return private:GetOperationTab("Auctioning") == 2 end
},
private:GetIsDoneStep(
L["Set Auction Settings"],
L["The first set of posting settings are under the 'Auction Settings' header. These control things like stack size and auction duration. Read the tooltips of the individual settings to see what they do and set them appropriately."]
),
private:GetIsDoneStep(
L["Set Auction Price Settings"],
L["The second set of posting settings are under the 'Auction Price Settings' header. These include the percentage of the buyout which the bid will be set to, and how much you want to undercut by. Read the tooltips of the individual settings to see what they do and set them appropriately."]
),
private:GetIsDoneStep(
L["Set Posting Price Settings"],
L["The final set of posting settings are under the 'Posting Price Settings' header. These define the price ranges which Auctioning will post your items within. Read the tooltips of the individual settings to see what they do and set them appropriately."]
),
},
["auctioningOperationCancel"] = {
{
title = L["Select the Cancel Tab"],
description = L["Select the 'Cancel' tab within the operation to set the canceling options for the TSM_Auctioning operation."],
isDone = function() return private:GetOperationTab("Auctioning") == 3 end
},
private:GetIsDoneStep(
L["Set Cancel Settings"],
L["These settings control when TSM_Auctioning will cancel your auctions. Read the tooltips of the individual settings to see what they do and set them appropriately."]
),
},
["openAuctioningAHTab"] = {
{
title = L["Open the Auction House"],
description = L["Go to the Auction House and open it."],
isDone = function() return AuctionFrame and AuctionFrame:IsVisible() end,
},
{
title = L["Click on the Auctioning Tab"],
description = L["Along the bottom of the AH are various tabs. Click on the 'Auctioning' AH tab."],
isDone = function() return TSMAPI:AHTabIsVisible("Auctioning") end,
},
},
["auctioningPostScan"] = {
{
title = L["Select Group and Start Scan"],
description = L["First, ensure your new group is selected in the group-tree and then click on the 'Start Post Scan' button at the bottom of the tab."],
isDone = private:GetEventIsDone("AUCTIONING:POST:START"),
},
},
["auctioningCancelScan"] = {
{
title = L["Select Group and Start Scan"],
description = L["First, ensure your new group is selected in the group-tree and then click on the 'Start Cancel Scan' button at the bottom of the tab."],
isDone = private:GetEventIsDone("AUCTIONING:CANCEL:START"),
},
},
["auctioningWaitForScan"] = {
{
title = L["Waiting for Scan to Finish"],
description = L["Please wait..."],
isDone = private:GetEventIsDone("AUCTIONING:SCANDONE"),
},
private:GetIsDoneStep(
L["Act on Scan Results"],
L["Now that the scan is finished, you can look through the results shown in the log, and for each item, decide what action you want to take.\n\nOnce you're done, click on the button below."]
),
},
}
private:PrependCreateOperationSteps(auctioningSteps["auctioningOperationPost"], "TSM_Auctioning", "Auctioning", L["A TSM_Auctioning operation will allow you to set rules for how auctionings are posted/canceled/reset on the auction house. To create one for this group, scroll down to the 'Auctioning' section, and click on the 'Create Auctioning Operation' button."], 3)
private:PrependCreateOperationSteps(auctioningSteps["auctioningOperationCancel"], "TSM_Auctioning", "Auctioning", L["A TSM_Auctioning operation will allow you to set rules for how auctionings are posted/canceled/reset on the auction house. To create one for this group, scroll down to the 'Auctioning' section, and click on the 'Create Auctioning Operation' button."], 3)
local shoppingSteps = {
["shoppingOperation"] = {
{
title = L["Select the 'General' Tab"],
description = L["Select the 'General' tab within the operation to set the general options for the TSM_Shopping operation."],
isDone = function() return private:GetOperationTab("Shopping") == 1 end
},
private:GetIsDoneStep(
L["Set a Maximum Price"],
L["The 'Maxium Auction Price (per item)' is the most you want to pay for the items you've added to your group. If you're not sure what to set this to and have TSM_AuctionDB installed (and it contains data from recent scans), you could try '90% dbmarket' for this option.\n\nOnce you're done adjusting this setting, click the button below."]
),
private:GetIsDoneStep(
L["Set Other Options"],
L["You can look through the tooltips of the other options to see what they do and decide if you want to change their values for this operation.\n\nOnce you're done, click on the button below."]
),
},
["openShoppingOptions"] = {
{
title = L["Click on the Module Operations / Options Icon"],
description = L["Along top of the window, on the left side, click on the 'Module Operations / Options' icon to open up the TSM module settings."],
isDone = function() return private:IsTSMFrameIconSelected(L["Module Operations / Options"]) end,
},
{
title = L["Click on the Shopping Tab"],
description = L["Select the 'Shopping' tab to open up the settings for TSM_Shopping."],
isDone = function() return private:GetOperationModuleSelection() == "Shopping" end,
},
{
title = L["Select the Options Page"],
description = L["Select the 'Options' page to change general settings for TSM_Shopping"],
isDone = function() return private:GetOperationTreeSelection("Shopping")[1] == "1" end,
},
},
["openShoppingAHTab"] = {
{
title = L["Open the Auction House"],
description = L["Go to the Auction House and open it."],
isDone = function() return AuctionFrame and AuctionFrame:IsVisible() end,
},
{
title = L["Click on the Shopping Tab"],
description = L["Along the bottom of the AH are various tabs. Click on the 'Shopping' AH tab."],
isDone = function() return TSMAPI:AHTabIsVisible("Shopping") end,
},
},
["shoppingGroupSearch"] = {
{
title = L["Show the 'TSM Groups' Sidebar Tab"],
description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'TSM Groups' one."],
isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "groups" end,
},
{
title = L["Select Group and Start Scan"],
description = L["First, ensure your new group is selected in the group-tree and then click on the 'Start Search' button at the bottom of the sidebar window."],
isDone = private:GetEventIsDone("SHOPPING:GROUPS:STARTSCAN"),
},
},
["shoppingFilterSearch"] = {
{
title = L["Show the 'Custom Filter' Sidebar Tab"],
description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'Custom Filter' one."],
isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "custom" end,
},
{
title = L["Enter Filters and Start Scan"],
description = L["You can use this sidebar window to help build AH searches. You can also type the filter directly in the search bar at the top of the AH window.\n\nEnter your filter and start the search."],
isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTFILTERSCAN"),
},
},
["shoppingSearchFromBags"] = {
{
title = L["Shift-Click Item in Your Bags"],
description = L["If you open your bags and shift-click the item in your bags, it will be placed in Shopping's search bar. You may need to put your cursor in the search bar first. Alternatively, you can type the name of the item manually in the search bar and then hit enter or click the 'Search' button."],
isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTFILTERSCAN"),
},
},
["shoppingOtherSidebar"] = {
{
title = L["Show the 'Other' Sidebar Tab"],
description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'Other' one."],
isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "other" end,
},
},
["shoppingVendorSearch"] = {
{
title = L["Start Vendor Search"],
description = L["Click on the 'Start Vendor Search' button in the sidebar window."],
isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTVENDORSCAN"),
},
},
["shoppingSniperSearch"] = {
{
title = L["Start Sniper"],
description = L["Click on the 'Start Sniper' button in the sidebar window."],
isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTSNIPER"),
},
private:GetIsDoneStep(
L["Sniping Scan in Progress"],
L["The 'Sniper' feature will constantly search the last page of the AH which shows items as they are being posted. This does not search existing auctions, but lets you buy items which are posted cheaply right as they are posted and buy them before anybody else can.\n\nYou can adjust the settings for what auctions are shown in TSM_Shopping's options.\n\nClick the button below when you're done reading this."]
),
},
["shoppingDestroySearch"] = {
{
title = L["Switch to Destroy Mode"],
description = L["Under the search bar, on the left, you can switch between normal and destroy mode for TSM_Shopping. Switch to 'Destroy Mode' now."],
isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSearchMode") == "destroy" end,
},
{
title = L["Start a Destroy Search"],
description = L["Type a raw material you would like to obtain via destroying in the search bar and start the search. For example: 'Ink of Dreams' or 'Spirit Dust'."],
isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTDESTROYSCAN"),
},
},
["shoppingWaitForScan"] = {
{
title = L["Waiting for Scan to Finish"],
description = L["Please wait..."],
isDone = function(self)
if private.stepData[self] then return true end
if not AuctionFrame:IsVisible() or not TSMAPI:AHTabIsVisible("Shopping") then return end
if not private.stepData.recentEvents["SHOPPING:SEARCH:SCANDONE"] then return end
local arg = private.stepData.recentEvents["SHOPPING:SEARCH:SCANDONE"].arg
wipe(private.stepData.recentEvents)
if arg == 0 then
TSM:Print(L["Looks like no items were found. You can either try searching for something else, or simply close the Assistant window if you're done."])
else
private.stepData[self] = true
return true
end
end,
},
},
["shoppingWaitForScanSilent"] = {
{
title = L["Waiting for Scan to Finish"],
description = L["Please wait..."],
isDone = private:GetEventIsDone("SHOPPING:SEARCH:SCANDONE"),
},
},
["shoppingPostFromResults"] = {
{
title = L["Post Your Items"],
description = L["If there are no auctions currently posted for this item, simmply click the 'Post' button at the bottom of the AH window. Otherwise, select the auction you'd like to undercut first."],
isDone = private:GetEventIsDone("TSM:AUCTIONCONTROL:POSTSHOWN"),
},
{
title = L["Adjust Post Parameters"],
description = L["In the confirmation window, you can adjust the buyout price, stack sizes, and auction duration. Once you're done, click the 'Post' button to post your items to the AH."],
isDone = private:GetEventIsDone("TSM:AUCTIONCONTROL:ITEMPOSTED"),
},
},
["shoppingQuickPostingSettings"] = {
private:GetIsDoneStep(
L["Set Quick Posting Duration"],
L["Underneath the 'Posting Options' header, there are two settings which control the Quick Posting feature of TSM_Shopping. The first one is the duration which Quick Posting should use when posting your items to the AH. Change this to your preferred duration for Quick Posting."]
),
private:GetIsDoneStep(
L["Set Quick Posting Price"],
L["Underneath the 'Posting Options' header, there are two settings which control the Quick Posting feature of TSM_Shopping. The second one is the price at which the Quick Posting will post items to the AH. This should generally not be a fixed gold value, since it will apply to every item. Change this setting to what you'd like to post items at with Quick Posting."]
),
},
["shoppingQuickPosting"] = {
{
title = L["Show the 'Quick Posting' Sidebar Tab"],
description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'Custom Filter' one."],
isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "quick" end,
},
{
title = L["Post an Item"],
description = L["Shift-Click an item in the sidebar window to immediately post it at your quick posting price."],
isDone = private:GetEventIsDone("SHOPPING:QUICKPOST:POSTEDITEM"),
},
},
}
private:PrependCreateOperationSteps(shoppingSteps["shoppingOperation"], "TSM_Shopping", "Shopping", L["A TSM_Shopping operation will allow you to set a maximum price we want to pay for the items in the group you just made. To create one for this group, scroll down to the 'Shopping' section, and click on the 'Create Shopping Operation' button."], 2)
do
Assistant.STEPS = {}
local addonSteps = {
["TradeSkillMaster"] = tsmSteps,
["TradeSkillMaster_Auctioning"] = auctioningSteps,
["TradeSkillMaster_Crafting"] = craftingSteps,
["TradeSkillMaster_Shopping"] = shoppingSteps,
}
for addon, moduleSteps in pairs(addonSteps) do
if select(4, GetAddOnInfo(addon)) then
for key, steps in pairs(moduleSteps) do
assert(not Assistant.STEPS[key], format("Multiples steps with key '%s' exist!", key))
Assistant.STEPS[key] = steps
end
end
end
end

824
TradeSkillMaster/Auction/AuctionControl.lua

@ -0,0 +1,824 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
TSMAPI.AuctionControl = {}
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionControl_private")
LibStub("AceEvent-3.0"):Embed(private)
private.matchList = {}
private.currentPage = {}
local function GetNumInBags(baseItemString)
local num = 0
for _, _, itemString, quantity in TSMAPI:GetBagIterator() do
if TSMAPI:GetBaseItemString(itemString) == baseItemString then
num = num + quantity
end
end
return num
end
local function ValidateAuction(index, list)
if not private.currentAuction then return end
local itemString, count, buyout, data, _
if type(list) == "table" then
itemString, count, buyout = unpack(list)
elseif type(list) == "string" then
itemString = TSMAPI:GetItemString(GetAuctionItemLink(list, index))
-- _, _, count, _, _, _, _, _, _, buyout = GetAuctionItemInfo(list, index)
_, _, count, _, _, _, _, _, buyout = GetAuctionItemInfo(list, index)
data = {itemString, count, buyout}
else
return
end
return count == private.currentAuction.count and buyout == private.currentAuction.buyout and itemString == private.currentAuction.itemString, data
end
local diffFrame = CreateFrame("Frame")
diffFrame:Hide()
diffFrame.num = 0
diffFrame:RegisterEvent("CHAT_MSG_SYSTEM")
diffFrame:RegisterEvent("UI_ERROR_MESSAGE")
diffFrame:SetScript("OnEvent", function(self, event, arg)
if event == "UI_ERROR_MESSAGE" then
if arg == ERR_ITEM_NOT_FOUND then
local auctionExists
for i=1, GetNumAuctionItems("list") do
if ValidateAuction(i, "list") then
auctionExists = true
break
end
end
if not auctionExists then
self.num = self.num - 1
end
elseif arg == ERR_AUCTION_HIGHER_BID then
local auctionExists
for i=1, GetNumAuctionItems("list") do
if ValidateAuction(i, "list") then
auctionExists = true
break
end
end
if not auctionExists then
self.num = self.num - 1
end
end
elseif event == "CHAT_MSG_SYSTEM" then
if arg == ERR_AUCTION_BID_PLACED then
self.num = self.num - 1
end
end
end)
local customPriceWarned
function private:SetCurrentAuction(record)
if not record then
private.currentAuction = nil
return
end
local buyout = record.buyout
if private.confirmationMode == "Post" and not record:IsPlayer() then
local undercut = TSMAPI:ParseCustomPrice(private.postUndercut)
undercut = undercut and undercut(record.parent:GetItemString())
if not undercut and not customPriceWarned then
TSM:Print(L["Invalid custom price for undercut amount. Using 1c instead."])
customPriceWarned = true
undercut = 1
end
buyout = buyout - undercut
end
private.currentAuction = {
link = record.parent.itemLink,
itemString = record.parent:GetItemString(),
buyout = buyout,
count = record.count,
numAuctions = record.numAuctions,
seller = record.seller,
isPlayer = record:IsPlayer(),
num = 1,
destroyingNum = record.parent.destroyingNum,
}
end
local count = 0
function private:FindCurrentAuctionForBuyout(noCache, resetCount)
if not private.currentAuction then return end
if diffFrame.num > 0 then
return TSMAPI:CreateTimeDelay(0.2, private.FindCurrentAuctionForBuyout)
end
if resetCount then
count = 0
end
count = count + 1
private:UpdateMatchList(true)
if #private.matchList > 0 then
-- the next item is on the current page
private:UpdateAuctionConfirmation()
return
end
private.matchList = {}
private.currentPage = {}
if count > 3 then
-- auction no longer exists
TSM:Print(L["Skipping auction which no longer exists."])
diffFrame.num = diffFrame.num - 1
private.justBought = true
private:AUCTION_ITEM_LIST_UPDATE()
return
end
TSMAPI.AuctionScan:FindAuction(private.OnAuctionFound, {itemString=private.currentAuction.itemString, buyout=private.currentAuction.buyout, count=private.currentAuction.count, seller=private.currentAuction.seller}, not noCache)
private.isSearching = true
end
function private:DoBuyout()
if private.isSearching or not private.currentAuction or not private.confirmationFrame:IsVisible() then return end
for i=#private.matchList, 1, -1 do
local aucIndex = private.matchList[i]
tremove(private.matchList, i)
tremove(private.currentPage, aucIndex)
if ValidateAuction(aucIndex, "list") then
PlaceAuctionBid("list", aucIndex, private.currentAuction.buyout)
private.justBought = true
diffFrame.num = diffFrame.num + 1
return
end
end
private:FindCurrentAuctionForBuyout()
end
function private:DoCancel()
if private.isSearching or not private.currentAuction or not private.confirmationFrame:IsVisible() then return end
local function OnCancel()
private.justBought = true
private:AUCTION_ITEM_LIST_UPDATE()
end
for i=GetNumAuctionItems("owner"), 1, -1 do
if ValidateAuction(i, "owner") then
CancelAuction(i)
-- wait for all the events that are triggered by this action
private:RegisterMessage("TSM_AH_EVENTS", OnCancel)
TSMAPI:WaitForAuctionEvents("Cancel")
return
end
end
TSM:Print(L["Auction not found. Skipped."])
private.justBought = true
private:AUCTION_ITEM_LIST_UPDATE()
end
function private:DoPost(postInfo)
if private.isSearching or not postInfo or not private.postFrame:IsVisible() then return end
if not AuctionFrameAuctions.duration then
-- Fix in case Blizzard_AuctionUI hasn't set this value yet (which could cause an error)
AuctionFrameAuctions.duration = postInfo.duration
end
local bag, slot
for b, s, itemString in TSMAPI:GetBagIterator() do
if postInfo.itemString == itemString then
bag, slot = b, s
break
end
end
if not bag then
TSM:Print(L["Item not found in bags. Skipping"])
return
end
local function OnPost()
private.postFrame:Hide()
postInfo.duration = postInfo.duration == 1 and 3 or 4
TSM:AuctionControlCallback("OnPost", postInfo)
TSMAPI:FireEvent("TSM:AUCTIONCONTROL:ITEMPOSTED", postInfo)
end
private:RegisterMessage("TSM_AH_EVENTS", OnPost)
TSMAPI:WaitForAuctionEvents("Post", postInfo.numAuctions > 1)
PickupContainerItem(bag, slot)
ClickAuctionSellItemButton(AuctionsItemButton, "LeftButton")
StartAuction(postInfo.bid, postInfo.buyout, postInfo.duration, postInfo.stackSize, postInfo.numAuctions)
end
function private:UpdateMatchList(noPageScanning)
private.matchList = {}
if noPageScanning then
for i=1, #private.currentPage do
if ValidateAuction(i, private.currentPage[i]) then
tinsert(private.matchList, i)
end
end
else
private.currentPage = {}
for i=1, GetNumAuctionItems("list") do
local isValid, data = ValidateAuction(i, "list")
private.currentPage[i] = data
if isValid then
tinsert(private.matchList, i)
end
end
end
end
function private:OnAuctionFound(cacheIndex)
if not private.isSearching or not private.currentAuction then return end
private.isSearching = nil
private:UpdateMatchList()
if #private.matchList == 0 then
private:FindCurrentAuctionForBuyout(true)
else
private.currentCacheIndex = cacheIndex
private:UpdateAuctionConfirmation()
end
end
function private:AUCTION_ITEM_LIST_UPDATE()
if not private.currentAuction or not TSMAPI:AHTabIsVisible(private.module) then return end
if private.justBought then
private.justBought = nil
private.currentAuction.num = private.currentAuction.num + 1
local prevAuction = CopyTable(private.currentAuction)
if private.currentAuction.num > private.currentAuction.numAuctions then
TSMAPI.AuctionControl:HideConfirmation()
else
if #private.matchList > 0 then
private:UpdateAuctionConfirmation()
else
private:FindCurrentAuctionForBuyout(nil, true)
end
end
if private.currentCacheIndex then
TSMAPI.AuctionScan:CacheRemove(prevAuction.itemString, private.currentCacheIndex)
private.currentCacheIndex = nil
end
TSM:AuctionControlCallback("OnBuyout", prevAuction)
end
end
function TSM:AuctionControlCallback(...)
if not private.callback then return end
private.callback(...)
end
-- **************************************************************************
-- Utility TSMAPI Functions
-- **************************************************************************
function TSMAPI.AuctionControl:IsConfirmationVisible()
return (private.confirmationFrame and private.confirmationFrame:IsVisible()) or (private.postFrame and private.postFrame:IsVisible())
end
function TSMAPI.AuctionControl:IsBuyingComplete()
return diffFrame.num <= 0
end
-- **************************************************************************
-- GUI Show/Hide/Update Functions
-- **************************************************************************
function TSMAPI.AuctionControl:ShowControlButtons(parent, rt, callback, module, postBidPercent, postUndercut)
private.confirmationFrame = private.confirmationFrame or private:CreateConfirmationFrame(parent)
private.postFrame = private.postFrame or private:CreatePostFrame(parent)
private.controlButtons = private.controlButtons or private:CreateControlButtons(parent)
private.controlButtons:Show()
private.rt = rt
private.callback = callback
private.module = module
private.postBidPercent = postBidPercent
private.postUndercut = postUndercut
return private.controlButtons
end
function TSMAPI.AuctionControl:HideControlButtons()
private.controlButtons:Hide()
private.rt = nil
private.callback = nil
TSMAPI.AuctionControl:HideConfirmation()
end
function TSMAPI.AuctionControl:SetNoResultItem(itemString, buyout)
if not itemString or not buyout then return end
local link = select(2, TSMAPI:GetSafeItemInfo(itemString))
private.currentAuction = {
link = link,
itemString = itemString,
buyout = buyout,
count = 1,
numAuctions = 1,
num = 1,
isNoResult = true,
}
end
function private:ShowConfirmationWindow()
if private.confirmationFrame:IsVisible() then
private.confirmationFrame:UpdateStrata()
return
elseif private.postFrame:IsVisible() then
private.postFrame:UpdateStrata()
return
end
private:SetCurrentAuction(private.rt:GetSelectedAuction())
if not private.currentAuction then return end
private:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
diffFrame.num = 0
diffFrame:Show()
private.confirmationFrame:Show()
private.confirmationFrame.proceed:Disable()
private.confirmationFrame.linkText:SetText("")
private.confirmationFrame.quantityText:SetText("")
private.confirmationFrame.buyoutText:SetText("")
private.confirmationFrame.buyoutText2:SetText("")
private.confirmationFrame.purchasedText:SetText("")
private.confirmationFrame.searchingText:SetText(L["Searching for item..."])
if private.confirmationMode == "Buyout" then
private:FindCurrentAuctionForBuyout(nil, true)
else
private:UpdateAuctionConfirmation()
end
end
function private:ShowPostWindow()
if private.confirmationFrame:IsVisible() then
private.confirmationFrame:UpdateStrata()
return
elseif private.postFrame:IsVisible() then
private.postFrame:UpdateStrata()
return
end
if not private.currentAuction or not private.currentAuction.isNoResult then
private:SetCurrentAuction(private.rt:GetSelectedAuction())
end
if not private.currentAuction then return end
private:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
diffFrame.num = 0
diffFrame:Show()
private.postFrame:Show()
private:UpdatePostFrame()
TSMAPI:FireEvent("TSM:AUCTIONCONTROL:POSTSHOWN")
end
function TSMAPI.AuctionControl:HideConfirmation()
private:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if private.confirmationFrame then private.confirmationFrame:Hide() end
if private.postFrame then private.postFrame:Hide() end
diffFrame:Hide()
private.isSearching = nil
private:SetCurrentAuction()
TSMAPI.AuctionScan:StopFindScan()
end
function private:UpdateAuctionConfirmation()
local buyoutText = TSMAPI:FormatTextMoneyIcon(private.currentAuction.buyout, nil, true)
local itemBuyoutText = TSMAPI:FormatTextMoneyIcon(floor(private.currentAuction.buyout/private.currentAuction.count), nil, true)
private.confirmationFrame.searchingText:SetText("")
private.confirmationFrame.linkText:SetText(private.currentAuction.link)
private.confirmationFrame.quantityText:SetText("x"..private.currentAuction.count)
private.confirmationFrame.buyoutText:SetText(format(L["Item Buyout: %s"], itemBuyoutText))
private.confirmationFrame.buyoutText2:SetText(format(L["Auction Buyout: %s"], buyoutText))
if private.confirmationMode == "Buyout" then
private.confirmationFrame.proceed:SetText(BUYOUT)
private.confirmationFrame.purchasedText:SetText(format(L["Purchasing Auction: %d/%d"], private.currentAuction.num, private.currentAuction.numAuctions))
elseif private.confirmationMode == "Cancel" then
private.confirmationFrame.proceed:SetText(CANCEL)
private.confirmationFrame.purchasedText:SetText(format(L["Canceling Auction: %d/%d"], private.currentAuction.num, private.currentAuction.numAuctions))
end
private.confirmationFrame.proceed:Enable()
end
function private:UpdatePostFrame()
local maxQuantity = select(8, TSMAPI:GetSafeItemInfo(private.currentAuction.link))
local numInBags = GetNumInBags(TSMAPI:GetBaseItemString(private.currentAuction.itemString))
local stackSize = min(private.currentAuction.count, numInBags)
local currentPerItem = floor(private.currentAuction.buyout/private.currentAuction.count)
local currentBuyout = stackSize == private.currentAuction.count and private.currentAuction.buyout or (currentPerItem*stackSize)
private.postFrame.numInBags = numInBags
private.postFrame.linkText:SetText(private.currentAuction.link)
private.postFrame.proceed:Enable()
private.postFrame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(currentBuyout, nil, nil, nil, true))
private.postFrame.perItemInputBox:SetText(TSMAPI:FormatTextMoney(currentPerItem, nil, nil, nil, true))
private.postFrame.numAuctionsInputBox.max = numInBags
private.postFrame.numAuctionsInputBox.btn:SetText(format(L["max %d"], floor(numInBags/stackSize)))
private.postFrame.numAuctionsInputBox:SetNumber(1)
private.postFrame.stackSizeInputBox.max = min(numInBags, maxQuantity)
private.postFrame.stackSizeInputBox.btn:SetText(format(L["max %d"], private.postFrame.stackSizeInputBox.max))
private.postFrame.stackSizeInputBox:SetNumber(stackSize)
private.postFrame.durationDropdown:SetValue(TSM.db.profile.postDuration)
end
-- **************************************************************************
-- GUI Creation Code
-- **************************************************************************
function private:CreateConfirmationFrame(parent)
local frame = CreateFrame("Frame", nil, parent)
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:Hide()
-- frame:SetPoint("CENTER")
frame:SetPoint("BOTTOMRIGHT")
frame:SetFrameStrata("DIALOG")
frame:SetWidth(300)
frame:SetHeight(150)
frame.UpdateStrata = function()
frame:SetFrameStrata("DIALOG")
frame.bg:SetFrameStrata("HIGH")
end
frame:SetScript("OnShow", frame.UpdateStrata)
frame:SetScript("OnUpdate", function()
if not TSMAPI:AHTabIsVisible(private.module) then
TSMAPI.AuctionControl:HideConfirmation()
end
end)
local bg = CreateFrame("Frame", nil, frame)
bg:SetFrameStrata("HIGH")
bg:SetPoint("TOPLEFT", parent.content)
bg:SetPoint("BOTTOMRIGHT", parent.content)
bg:EnableMouse(true)
TSMAPI.Design:SetFrameBackdropColor(bg)
bg:SetAlpha(.2)
frame.bg = bg
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHConfirmationActionButton")
btn:SetPoint("BOTTOMLEFT", 10, 10)
btn:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -2, 10)
btn:SetHeight(25)
btn:SetText("")
btn:SetScript("OnClick", function(self)
if not TSMAPI:AHTabIsVisible(private.module) then return end
self:Disable()
if private.confirmationMode == "Buyout" then
private:DoBuyout()
elseif private.confirmationMode == "Cancel" then
private:DoCancel()
end
end)
frame.proceed = btn
local btn = TSMAPI.GUI:CreateButton(frame, 18)
btn:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 2, 10)
btn:SetPoint("BOTTOMRIGHT", -10, 10)
btn:SetHeight(25)
btn:SetText(CLOSE)
btn:SetScript("OnClick", function() frame:Hide() end)
frame.close = btn
local linkText = TSMAPI.GUI:CreateLabel(frame)
linkText:SetFontObject(GameFontNormal)
linkText:SetPoint("TOP", -10, -10)
frame.linkText = linkText
local bg = frame:CreateTexture(nil, "BACKGROUND")
bg:SetPoint("TOPLEFT", linkText, -2, 2)
bg:SetPoint("BOTTOMRIGHT", linkText, 2, -2)
TSMAPI.Design:SetContentColor(bg)
linkText.bg = bg
bg:Show()
local quantityText = TSMAPI.GUI:CreateLabel(frame)
quantityText:SetPoint("LEFT", linkText, "RIGHT")
frame.quantityText = quantityText
local buyoutText = TSMAPI.GUI:CreateLabel(frame)
buyoutText:SetPoint("TOPLEFT", 10, -41)
buyoutText:SetJustifyH("LEFT")
frame.buyoutText = buyoutText
local buyoutText2 = TSMAPI.GUI:CreateLabel(frame)
buyoutText2:SetPoint("TOPLEFT", buyoutText, "BOTTOMLEFT")
buyoutText2:SetJustifyH("LEFT")
frame.buyoutText2 = buyoutText2
local purchasedText = TSMAPI.GUI:CreateLabel(frame)
purchasedText:SetPoint("TOPLEFT", 10, -70)
frame.purchasedText = purchasedText
local searchingText = TSMAPI.GUI:CreateLabel(frame)
searchingText:SetPoint("CENTER")
frame.searchingText = searchingText
return frame
end
function private:CreatePostFrame(parent)
local frame = CreateFrame("Frame", nil, parent)
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:Hide()
frame:SetPoint("CENTER")
frame:SetFrameStrata("DIALOG")
frame:SetWidth(250)
frame:SetHeight(245)
frame.UpdateStrata = function()
frame:SetFrameStrata("DIALOG")
frame.bg:SetFrameStrata("HIGH")
end
frame:SetScript("OnShow", frame.UpdateStrata)
frame:SetScript("OnUpdate", function()
if not TSMAPI:AHTabIsVisible(private.module) then
TSMAPI.AuctionControl:HideConfirmation()
end
end)
local bg = CreateFrame("Frame", nil, frame)
bg:SetFrameStrata("HIGH")
bg:SetPoint("TOPLEFT", parent.content)
bg:SetPoint("BOTTOMRIGHT", parent.content)
bg:EnableMouse(true)
TSMAPI.Design:SetFrameBackdropColor(bg)
bg:SetAlpha(0.2)
frame.bg = bg
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHConfirmationPostButton")
btn:SetPoint("BOTTOMLEFT", 10, 10)
btn:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -2, 10)
btn:SetHeight(25)
btn:SetText(L["Post"])
btn:SetScript("OnClick", function(self)
if not TSMAPI:AHTabIsVisible(private.module) then return end
self:Disable()
local postInfo = {}
postInfo.itemString = private.currentAuction.itemString
postInfo.buyout = TSMAPI:UnformatTextMoney(frame.buyoutInputBox:GetText())
postInfo.bid = max(floor(postInfo.buyout*private.postBidPercent), 1)
postInfo.stackSize = frame.stackSizeInputBox:GetNumber()
postInfo.numAuctions = frame.numAuctionsInputBox:GetNumber()
postInfo.duration = TSM.db.profile.postDuration
private:DoPost(postInfo)
end)
frame.proceed = btn
local btn = TSMAPI.GUI:CreateButton(frame, 18)
btn:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 2, 10)
btn:SetPoint("BOTTOMRIGHT", -10, 10)
btn:SetHeight(25)
btn:SetText(CLOSE)
btn:SetScript("OnClick", function() frame:Hide() end)
frame.close = btn
local linkText = TSMAPI.GUI:CreateLabel(frame)
linkText:SetFontObject(GameFontNormal)
linkText:SetPoint("TOP", -10, -10)
frame.linkText = linkText
local bg = frame:CreateTexture(nil, "BACKGROUND")
bg:SetPoint("TOPLEFT", linkText, -2, 2)
bg:SetPoint("BOTTOMRIGHT", linkText, 2, -2)
TSMAPI.Design:SetContentColor(bg)
linkText.bg = bg
bg:Show()
local function OnPriceInputBoxTextChanged()
local buyout = TSMAPI:UnformatTextMoney(frame.buyoutInputBox:GetText())
local perItem = TSMAPI:UnformatTextMoney(frame.perItemInputBox:GetText())
if not buyout or not perItem or buyout == 0 then
frame.proceed:Disable()
else
frame.proceed:Enable()
end
end
local function OnPriceInputBoxEditFocusLost(self)
local copper = TSMAPI:UnformatTextMoney(self:GetText())
if copper then
local stackSize = frame.stackSizeInputBox:GetNumber()
if self == frame.buyoutInputBox then
frame.perItemInputBox:SetText(TSMAPI:FormatTextMoney(floor(copper/stackSize), nil, nil, nil, true))
elseif self == frame.perItemInputBox then
frame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(copper*stackSize, nil, nil, nil, true))
end
self:SetText(TSMAPI:FormatTextMoney(copper, nil, nil, nil, true))
self:ClearFocus()
else
self:SetFocus()
end
end
local function OnInputBoxTabPressed(self)
local boxes = {"buyoutInputBox", "perItemInputBox", "numAuctionsInputBox", "stackSizeInputBox"}
self:ClearFocus()
for i=1, #boxes-1 do
if self == frame[boxes[i]] then
frame[boxes[i+1]]:SetFocus()
end
end
end
local buyoutLabel = TSMAPI.GUI:CreateLabel(frame)
buyoutLabel:SetPoint("TOPLEFT", 10, -40)
buyoutLabel:SetHeight(20)
buyoutLabel:SetJustifyH("LEFT")
buyoutLabel:SetText(L["Auction Buyout:"])
local buyoutInputBox = TSMAPI.GUI:CreateInputBox(frame)
buyoutInputBox:SetJustifyH("RIGHT")
buyoutInputBox:SetPoint("TOPRIGHT", -10, -40)
buyoutInputBox:SetPoint("TOPLEFT", buyoutLabel, "TOPRIGHT", 10, 0)
buyoutInputBox:SetHeight(20)
buyoutInputBox:SetScript("OnEnterPressed", buyoutInputBox.ClearFocus)
buyoutInputBox:SetScript("OnEscapePressed", buyoutInputBox.ClearFocus)
buyoutInputBox:SetScript("OnEditFocusLost", OnPriceInputBoxEditFocusLost)
buyoutInputBox:SetScript("OnTextChanged", OnPriceInputBoxTextChanged)
buyoutInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.buyoutInputBox = buyoutInputBox
local perItemLabel = TSMAPI.GUI:CreateLabel(frame)
perItemLabel:SetPoint("TOPLEFT", 10, -65)
perItemLabel:SetHeight(20)
perItemLabel:SetJustifyH("LEFT")
perItemLabel:SetText(L["Per Item:"])
local perItemInputBox = TSMAPI.GUI:CreateInputBox(frame)
perItemInputBox:SetJustifyH("RIGHT")
perItemInputBox:SetPoint("TOPRIGHT", -10, -65)
perItemInputBox:SetPoint("TOPLEFT", perItemLabel, "TOPRIGHT", 10, 0)
perItemInputBox:SetHeight(20)
perItemInputBox:SetScript("OnEnterPressed", perItemInputBox.ClearFocus)
perItemInputBox:SetScript("OnEscapePressed", perItemInputBox.ClearFocus)
perItemInputBox:SetScript("OnEditFocusLost", OnPriceInputBoxEditFocusLost)
perItemInputBox:SetScript("OnTextChanged", OnPriceInputBoxTextChanged)
perItemInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.perItemInputBox = perItemInputBox
local function OnCountInputBoxEditFocusLost(self)
local numAuctions = max(1, min(frame.numAuctionsInputBox:GetNumber(), frame.numAuctionsInputBox.max))
local stackSize = max(1, min(frame.stackSizeInputBox:GetNumber(), frame.stackSizeInputBox.max))
if self == frame.stackSizeInputBox then
numAuctions = min(numAuctions, floor(frame.numInBags/stackSize))
elseif self == frame.numAuctionsInputBox then
stackSize = min(stackSize, floor(frame.numInBags/numAuctions))
end
frame.numAuctionsInputBox:SetNumber(numAuctions)
frame.stackSizeInputBox:SetNumber(stackSize)
frame.numAuctionsInputBox.btn:SetText(format(L["max %d"], floor(frame.numInBags/stackSize)))
frame.stackSizeInputBox.btn:SetText(format(L["max %d"], min(frame.stackSizeInputBox.max, floor(frame.numInBags/numAuctions))))
local perItem = TSMAPI:UnformatTextMoney(frame.perItemInputBox:GetText())
frame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(perItem*stackSize, nil, nil, nil, true))
end
local function OnCountInputBoxTextChanged(self)
local numAuctions = frame.numAuctionsInputBox:GetNumber()
local stackSize = frame.stackSizeInputBox:GetNumber()
if numAuctions <= 0 or stackSize <= 0 or numAuctions*stackSize > frame.numInBags then
frame.proceed:Disable()
else
frame.proceed:Enable()
end
end
local function OnMaxButtonClicked(self)
self.inputBox:SetNumber(self.inputBox.max)
self.inputBox:SetFocus()
self.inputBox:ClearFocus()
end
local numAuctionsInputBox = TSMAPI.GUI:CreateInputBox(frame)
numAuctionsInputBox:SetJustifyH("CENTER")
numAuctionsInputBox:SetNumeric(true)
numAuctionsInputBox:SetPoint("TOPLEFT", 10, -110)
numAuctionsInputBox:SetHeight(20)
numAuctionsInputBox:SetScript("OnEnterPressed", numAuctionsInputBox.ClearFocus)
numAuctionsInputBox:SetScript("OnEscapePressed", numAuctionsInputBox.ClearFocus)
numAuctionsInputBox:SetScript("OnEditFocusLost", OnCountInputBoxEditFocusLost)
numAuctionsInputBox:SetScript("OnTextChanged", OnCountInputBoxTextChanged)
numAuctionsInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.numAuctionsInputBox = numAuctionsInputBox
local stackSizeInputBox = TSMAPI.GUI:CreateInputBox(frame)
stackSizeInputBox:SetJustifyH("CENTER")
stackSizeInputBox:SetNumeric(true)
stackSizeInputBox:SetPoint("TOPRIGHT", -10, -110)
stackSizeInputBox:SetHeight(20)
stackSizeInputBox:SetScript("OnEnterPressed", stackSizeInputBox.ClearFocus)
stackSizeInputBox:SetScript("OnEscapePressed", stackSizeInputBox.ClearFocus)
stackSizeInputBox:SetScript("OnEditFocusLost", OnCountInputBoxEditFocusLost)
stackSizeInputBox:SetScript("OnTextChanged", OnCountInputBoxTextChanged)
stackSizeInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.stackSizeInputBox = stackSizeInputBox
local countLabel = TSMAPI.GUI:CreateLabel(frame)
countLabel:SetPoint("TOPLEFT", numAuctionsInputBox, "TOPRIGHT", 10, 0)
countLabel:SetPoint("TOPRIGHT", stackSizeInputBox, "TOPLEFT", -10, 0)
countLabel:SetHeight(20)
countLabel:SetJustifyH("CENTER")
countLabel:SetText(L["stacks of"])
local editboxWidth = (frame:GetWidth() - 40 - countLabel:GetStringWidth()) / 2
numAuctionsInputBox:SetWidth(editboxWidth)
stackSizeInputBox:SetWidth(editboxWidth)
local maxStackSizeBtn = TSMAPI.GUI:CreateButton(frame, 12)
maxStackSizeBtn:SetPoint("TOPLEFT", stackSizeInputBox, "BOTTOMLEFT", 5, -3)
maxStackSizeBtn:SetPoint("TOPRIGHT", stackSizeInputBox, "BOTTOMRIGHT", -5, -3)
maxStackSizeBtn:SetHeight(14)
maxStackSizeBtn:SetText("")
maxStackSizeBtn:SetScript("OnClick", OnMaxButtonClicked)
maxStackSizeBtn.inputBox = stackSizeInputBox
stackSizeInputBox.btn = maxStackSizeBtn
local maxNumAuctionsBtn = TSMAPI.GUI:CreateButton(frame, 12)
maxNumAuctionsBtn:SetPoint("TOPLEFT", numAuctionsInputBox, "BOTTOMLEFT", 5, -3)
maxNumAuctionsBtn:SetPoint("TOPRIGHT", numAuctionsInputBox, "BOTTOMRIGHT", -5, -3)
maxNumAuctionsBtn:SetHeight(14)
maxNumAuctionsBtn:SetText("")
maxNumAuctionsBtn:SetScript("OnClick", OnMaxButtonClicked)
maxNumAuctionsBtn.inputBox = numAuctionsInputBox
numAuctionsInputBox.btn = maxNumAuctionsBtn
local durationLabel = TSMAPI.GUI:CreateLabel(frame)
durationLabel:SetPoint("TOPLEFT", 10, -165)
durationLabel:SetHeight(20)
durationLabel:SetJustifyH("LEFT")
durationLabel:SetText(L["Duration:"])
local list = {AUCTION_DURATION_ONE, AUCTION_DURATION_TWO, AUCTION_DURATION_THREE}
local durationDropdown = TSMAPI.GUI:CreateDropdown(frame, list)
durationDropdown:SetPoint("TOPLEFT", durationLabel, "TOPRIGHT", 10, 0)
durationDropdown:SetPoint("TOPRIGHT", 0, -165)
durationDropdown:SetHeight(20)
durationDropdown:SetCallback("OnValueChanged", function(self, _, value) TSM.db.profile.postDuration = value end)
frame.durationDropdown = durationDropdown
return frame
end
function private:CreateControlButtons(parent)
local frame = CreateFrame("Frame", nil, parent)
frame:SetHeight(24)
frame:SetWidth(390)
frame:SetPoint("BOTTOMRIGHT", -20, 6)
local function OnClick(self)
if not private.rt or not private.callback then return end
private.confirmationMode = self.which
if self.which == "Post" then
private:ShowPostWindow()
else
private:ShowConfirmationWindow()
end
end
local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabCancelButton")
button:SetPoint("TOPLEFT", 0, 0)
button:SetWidth(100)
button:SetHeight(24)
button:SetText(CANCEL)
button.which = "Cancel"
button:SetScript("OnClick", OnClick)
frame.cancel = button
local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabPostButton")
button:SetPoint("TOPLEFT", 104, 0)
button:SetWidth(100)
button:SetHeight(24)
button:SetText(L["Post"])
button.which = "Post"
button:SetScript("OnClick", OnClick)
frame.post = button
local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabBuyoutButton")
button:SetPoint("TOPLEFT", 208, 0)
button:SetWidth(100)
button:SetHeight(24)
button:SetText(BUYOUT)
button.which = "Buyout"
button:SetScript("OnClick", OnClick)
frame.buyout = button
return frame
end

298
TradeSkillMaster/Auction/AuctionFrame.lua

@ -0,0 +1,298 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local private = {auctionTabs={}, queuedTabs={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionFrame_private")
LibStub("AceEvent-3.0"):Embed(private)
LibStub("AceHook-3.0"):Embed(private)
local registeredModules = {}
function TSM:RegisterAuctionFunction(moduleName, callbackShow, callbackHide)
if registeredModules[moduleName] then return end
registeredModules[moduleName] = true
if AuctionFrame then
private:CreateTSMAHTab(moduleName, callbackShow, callbackHide)
else
tinsert(private.queuedTabs, {moduleName, callbackShow, callbackHide})
end
end
function private:CreateTSMAHTab(moduleName, callbackShow, callbackHide)
local auctionTab = CreateFrame("Frame", nil, AuctionFrame)
auctionTab:Hide()
auctionTab:SetAllPoints()
auctionTab:EnableMouse(true)
auctionTab:SetMovable(true)
auctionTab:SetScript("OnMouseDown", function() if AuctionFrame:IsMovable() then AuctionFrame:StartMoving() end end)
auctionTab:SetScript("OnMouseUp", function() if AuctionFrame:IsMovable() then AuctionFrame:StopMovingOrSizing() end end)
TSMAPI:CancelFrame("blizzAHLoadedDelay")
local n = AuctionFrame.numTabs + 1
local tab = CreateFrame("Button", "AuctionFrameTab"..n, AuctionFrame, "AuctionTabTemplate")
tab:Hide()
tab:SetID(n)
tab:SetText(TSMAPI.Design:GetInlineColor("link2")..moduleName.."|r")
tab:SetNormalFontObject(GameFontHighlightSmall)
tab.isTSMTab = moduleName
tab:SetPoint("LEFT", _G["AuctionFrameTab"..n-1], "RIGHT", -8, 0)
tab:Show()
PanelTemplates_SetNumTabs(AuctionFrame, n)
PanelTemplates_EnableTab(AuctionFrame, n)
auctionTab.tab = tab
local closeBtn = TSMAPI.GUI:CreateButton(auctionTab, 18)
closeBtn:SetPoint("BOTTOMRIGHT", -5, 5)
closeBtn:SetWidth(75)
closeBtn:SetHeight(24)
closeBtn:SetText(CLOSE)
closeBtn:SetScript("OnClick", CloseAuctionHouse)
local iconFrame = CreateFrame("Frame", nil, auctionTab)
iconFrame:SetPoint("CENTER", auctionTab, "TOPLEFT", 30, -30)
iconFrame:SetHeight(100)
iconFrame:SetWidth(100)
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
icon:SetAllPoints()
icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Big")
local textFrame = CreateFrame("Frame", nil, auctionTab)
local iconText = textFrame:CreateFontString(nil, "OVERLAY")
iconText:SetPoint("CENTER", iconFrame)
iconText:SetHeight(15)
iconText:SetJustifyH("CENTER")
iconText:SetJustifyV("CENTER")
iconText:SetFont(TSMAPI.Design:GetContentFont("normal"))
iconText:SetTextColor(165/255, 168/255, 188/255, .7)
local version = TSM._version
iconText:SetText(version)
local ag = iconFrame:CreateAnimationGroup()
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(1)
spin:SetDuration(2)
spin:SetDegrees(90)
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(2)
spin:SetDuration(4)
spin:SetDegrees(-180)
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(3)
spin:SetDuration(2)
spin:SetDegrees(90)
ag:SetLooping("REPEAT")
iconFrame:SetScript("OnEnter", function() ag:Play() end)
iconFrame:SetScript("OnLeave", function() ag:Stop() end)
local moneyText = TSMAPI.GUI:CreateTitleLabel(auctionTab, 16)
moneyText:SetJustifyH("CENTER")
moneyText:SetJustifyV("CENTER")
moneyText:SetPoint("CENTER", auctionTab, "BOTTOMLEFT", 85, 17)
TSMAPI.Design:SetIconRegionColor(moneyText)
moneyText.SetMoney = function(self, money)
self:SetText(TSMAPI:FormatTextMoneyIcon(money))
end
auctionTab.moneyText = moneyText
local moneyTextFrame = CreateFrame("Frame", nil, auctionTab)
moneyTextFrame:SetAllPoints(moneyText)
moneyTextFrame:EnableMouse(true)
moneyTextFrame:SetScript("OnEnter", function(self)
local currentTotal = 0
local incomingTotal = 0
for i=1, GetNumAuctionItems("owner") do
-- local count, _, _, _, _, _, _, buyoutAmount = select(3, GetAuctionItemInfo("owner", i))
local count, _, _, _, _, _, buyoutAmount = select(3, GetAuctionItemInfo("owner", i))
if count == 0 then
incomingTotal = incomingTotal + buyoutAmount
else
currentTotal = currentTotal + buyoutAmount
end
end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine("Gold Info:")
GameTooltip:AddDoubleLine("Player Gold", TSMAPI:FormatTextMoneyIcon(GetMoney()), 1, 1, 1, 1, 1, 1)
GameTooltip:AddDoubleLine("Incoming Auction Sales", TSMAPI:FormatTextMoneyIcon(incomingTotal), 1, 1, 1, 1, 1, 1)
GameTooltip:AddDoubleLine("Current Auctions Value", TSMAPI:FormatTextMoneyIcon(currentTotal), 1, 1, 1, 1, 1, 1)
GameTooltip:Show()
end)
moneyTextFrame:SetScript("OnLeave", function()
GameTooltip:ClearLines()
GameTooltip:Hide()
end)
auctionTab:SetScript("OnShow", function(self)
self:SetAllPoints()
if not self.minimized then
callbackShow(self)
end
end)
auctionTab:SetScript("OnHide", function(self)
if not self.minimized then
callbackHide()
end
end)
local contentFrame = CreateFrame("Frame", nil, auctionTab)
contentFrame:SetPoint("TOPLEFT", 4, -80)
contentFrame:SetPoint("BOTTOMRIGHT", -4, 35)
TSMAPI.Design:SetContentColor(contentFrame)
auctionTab.content = contentFrame
tinsert(private.auctionTabs, auctionTab)
end
function private:InitializeAuctionFrame(auctionTab)
-- make the AH movable if this option is enabled
AuctionFrame:SetMovable(TSM.db.profile.auctionFrameMovable)
AuctionFrame:EnableMouse(true)
AuctionFrame:SetScript("OnMouseDown", function(self) if self:IsMovable() then self:StartMoving() end end)
AuctionFrame:SetScript("OnMouseUp", function(self) if self:IsMovable() then self:StopMovingOrSizing() end end)
-- scale the auction frame according to the TSM option
if AuctionFrame:GetScale() ~= 1 and TSM.db.profile.auctionFrameScale == 1 then TSM.db.profile.auctionFrameScale = AuctionFrame:GetScale() end
AuctionFrame:SetScale(TSM.db.profile.auctionFrameScale)
local prevTab
local function TabChangeHook(self)
if self.isTSMTab then
for _, tabFrame in ipairs(private.auctionTabs) do
if tabFrame.minimized and tabFrame.tab ~= self then
tabFrame:Show()
tabFrame.minimized = nil
tabFrame:Hide()
elseif tabFrame:IsShown() then
tabFrame:Hide()
end
end
local tabAuctionFrame = private:GetAuctionFrame(self)
private:OnTabClick(tabAuctionFrame)
AuctionFrame:SetFrameLevel(1)
tabAuctionFrame:SetFrameStrata(AuctionFrame:GetFrameStrata())
tabAuctionFrame:SetFrameLevel(AuctionFrame:GetFrameLevel() + 1)
elseif prevTab and prevTab.isTSMTab then
local prevTabAuctionFrame = private:GetAuctionFrame(prevTab)
prevTabAuctionFrame.minimized = true
prevTabAuctionFrame:Hide()
private:TabHidden()
end
prevTab = self
end
private:Hook("AuctionFrameTab_OnClick", TabChangeHook, true)
-- Makes sure the TSM tab hides correctly when used with addons that hook this function to change tabs (ie Auctionator)
-- This probably doesn't have to be a SecureHook, but does need to be a Post-Hook.
private:SecureHook("ContainerFrameItemButton_OnModifiedClick", function()
if _G["AuctionFrameTab"..PanelTemplates_GetSelectedTab(AuctionFrame)].isTSMTab then return end
TabChangeHook(_G["AuctionFrameTab"..PanelTemplates_GetSelectedTab(AuctionFrame)])
end)
end
function private:GetAuctionFrame(targetTab)
for _, tabFrame in ipairs(private.auctionTabs) do
if tabFrame.tab == targetTab then
return tabFrame
end
end
end
function private:InitializeAHTab()
for _, info in ipairs(private.queuedTabs) do
private:CreateTSMAHTab(unpack(info))
end
private.queuedTabs = {}
private:InitializeAuctionFrame()
private.isInitialized = true
if AuctionHouse and AuctionHouse:IsVisible() then
private:AUCTION_HOUSE_SHOW()
end
end
function TSMAPI:AHTabIsVisible(module)
return module and _G["AuctionFrameTab"..AuctionFrame.selectedTab].isTSMTab == module
end
function private:AUCTION_HOUSE_SHOW()
if private.isInitialized then
for i = AuctionFrame.numTabs, 1, -1 do
local text = gsub(_G["AuctionFrameTab"..i]:GetText(), "|r", "")
text = gsub(text, "|c[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]", "")
if text == TSM.db.profile.defaultAuctionTab then
_G["AuctionFrameTab"..i]:Click()
return
end
end
_G["AuctionFrameTab1"]:Click()
end
end
function private:OnTabClick(tab)
AuctionFrameTopLeft:Hide()
AuctionFrameTop:Hide()
AuctionFrameTopRight:Hide()
AuctionFrameBotLeft:Hide()
AuctionFrameBot:Hide()
AuctionFrameBotRight:Hide()
AuctionFrameMoneyFrame:Hide()
AuctionFrameCloseButton:Hide()
private:RegisterEvent("PLAYER_MONEY")
if TSM.db.profile.openAllBags then
OpenAllBags(true)
end
TSMAPI:CreateTimeDelay("hideAHMoneyFrame", 0.1, function() AuctionFrameMoneyFrame:Hide() end)
TSMAPI.Design:SetFrameBackdropColor(tab)
AuctionFrameTab1:SetPoint("TOPLEFT", AuctionFrame, "BOTTOMLEFT", 15, 1)
tab:Show()
tab.minimized = nil
tab.moneyText:SetMoney(GetMoney())
end
function private:TabHidden()
AuctionFrameTopLeft:Show()
AuctionFrameTop:Show()
AuctionFrameTopRight:Show()
AuctionFrameBotLeft:Show()
AuctionFrameBot:Show()
AuctionFrameBotRight:Show()
AuctionFrameMoneyFrame:Show()
AuctionFrameCloseButton:Show()
AuctionFrameTab1:SetPoint("TOPLEFT", AuctionFrame, "BOTTOMLEFT", 15, 12)
end
function private:PLAYER_MONEY()
for _, tab in ipairs(private.auctionTabs) do
if tab:IsVisible() then
tab.moneyText:SetMoney(GetMoney())
end
end
end
function private:ADDON_LOADED(event, addonName)
if addonName == "Blizzard_AuctionUI" then
private:UnregisterEvent("ADDON_LOADED")
if TSM.db then
private:InitializeAHTab()
else
TSMAPI:CreateTimeDelay("blizzAHLoadedDelay", 0.2, private.InitializeAHTab, 0.2)
end
end
end
do
private:RegisterEvent("AUCTION_HOUSE_SHOW")
if IsAddOnLoaded("Blizzard_AuctionUI") then
private:InitializeAHTab()
else
private:RegisterEvent("ADDON_LOADED")
end
end

433
TradeSkillMaster/Auction/AuctionItem.lua

@ -0,0 +1,433 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for the AuctionItem objects
local TSM = select(2, ...)
local NewRecord, sortHelpers
local AuctionRecord = {
Initialize = function(self)
self.objType = "AuctionRecord"
end,
SetData = function(self, parent, count, minBid, minIncrement, buyout, bid, highBidder, seller, timeLeft)
self.parent = parent
self.count = count
self.minBid = minBid
self.minIncrement = minIncrement
self.buyout = buyout
self.bid = bid
self.highBidder = highBidder
self.seller = seller
self.timeLeft = timeLeft
end,
IsPlayer = function(self)
return TSMAPI:IsPlayer(self.seller) or self.parent.alts[self.seller]
end,
GetPercent = function(self)
local itemBuyout = self:GetItemBuyout()
local marketValue = self.parent.marketValue
if itemBuyout and marketValue then
return (itemBuyout / marketValue) * 100
end
end,
GetDisplayedBid = function(self)
local displayedBid
if self.bid == 0 then
displayedBid = self.minBid
else
displayedBid = self.bid
end
return displayedBid
end,
GetRequiredBid = function(self)
local requiredBid
if self.bid == 0 then
requiredBid = self.minBid
else
requiredBid = self.bid + self.minIncrement
end
return requiredBid
end,
GetItemBuyout = function(self)
if not self.buyout or self.buyout == 0 then return end
return floor(self.buyout / self.count)
end,
GetItemDisplayedBid = function(self)
return floor(self:GetDisplayedBid() / self.count)
end,
GetItemDestroyingBuyout = function(self)
local itemBuyout = self:GetItemBuyout()
if itemBuyout then
return itemBuyout * self.parent.destroyingNum
end
end,
GetItemDestroyingDisplayedBid = function(self)
local itemBid = self:GetItemDisplayedBid()
if itemBid then
return itemBid * self.parent.destroyingNum
end
end,
Copy = function(self)
local o = NewRecord()
o:SetData(self.parent, self.count, self.minBid, self.minIncrement, self.buyout, self.bid, self.highBidder, self.seller, self.timeLeft)
o.uniqueID = self.uniqueID
return o
end,
Equals = function(self, other)
if self == other then
return true
end
local params = self.parent.recordParams
for _, key in ipairs(params) do
if type(self[key]) == "function" then
if self[key](self) ~= other[key](other) then
return false
end
else
if self[key] ~= other[key] then
return false
end
end
end
return true
end,
}
NewRecord = function()
local o = {}
setmetatable(o, AuctionRecord)
AuctionRecord.__index = AuctionRecord
o:Initialize()
return o
end
--- Test Documentation
-- @name AuctionItem
-- @description Test description for the Auction Item object.
local AuctionItem = {
-- @field Test field
Initialize = function(self)
self.objType = "AuctionItem"
self.itemLink = nil
self.marketValue = nil
self.playerAuctions = 0
self.records = {}
self.alts = {}
self.recordParams = {"buyout", "count", "seller"}
self.shouldCompact = true
self.texture = ""
end,
-- sets the item (or battle pet's) texture
SetTexture = function(self, texture)
self.texture = texture
end,
-- gets the item (or battle pet's) texture
GetTexture = function(self)
return self.texture
end,
-- sets the alts table used for making other players count as the current player
SetAlts = function(self, alts)
self.alts = alts
end,
-- sets the list of params we care about
SetRecordParams = function(self, params)
self.recordParams = params
end,
-- sets the itemLink
SetItemLink = function(self, itemLink)
self.itemLink = itemLink
end,
-- returns the itemString
GetItemString = function(self)
return TSMAPI:GetItemString(self.itemLink)
end,
-- returns the itemID
GetItemID = function(self)
return TSMAPI:GetItemID(self.itemLink)
end,
-- adds a record
AddAuctionRecord = function(self, ...)
local record = NewRecord()
record:SetData(self, ...)
-- if strfind(self.itemLink, "battlepet") then
-- record.uniqueID = table.concat({TSMAPI:Select({2, 3, 4, 5, 6, 7}, (":"):split(self.itemLink))}, ".")
-- else
record.uniqueID = select(9, (":"):split(self.itemLink))
-- end
self:AddRecord(record)
end,
-- adds a record
AddRecord = function(self, record)
self.shouldCompact = true
if record:IsPlayer() then
self.playerAuctions = self.playerAuctions + 1
end
tinsert(self.records, record)
end,
-- sorts the records using the passed sortFunc
SortRecords = function(self, sortFunc)
sort(self.records, sortFunc)
end,
-- sets the market value of this item
SetMarketValue = function(self, value)
self.marketValue = value
end,
-- sorts all the records in ascending order by buyout > bid > count > seller
DoDefaultSort = function(self)
self:SortRecords(function(a, b)
local aBuyout = a:GetItemBuyout()
local bBuyout = b:GetItemBuyout()
if not aBuyout or aBuyout == 0 then
return false
end
if not bBuyout or bBuyout == 0 then
return true
end
if aBuyout == bBuyout then
if a.seller == b.seller then
if a.count == b.count then
local aBid = a:GetItemDisplayedBid()
local bBid = b:GetItemDisplayedBid()
return aBid < bBid
end
return a.count < b.count
end
return a.seller < b.seller
end
return aBuyout < bBuyout
end)
end,
-- populates the compactRecords table
PopulateCompactRecords = function(self, sortParams, isAscending)
if self.shouldCompact then
self.shouldCompact = false
self.compactRecords = {}
self:DoDefaultSort()
local currentRecord
for _, record in ipairs(self.records) do
local temp = record:Copy()
if not currentRecord or not temp:Equals(currentRecord) then
currentRecord = temp
currentRecord.numAuctions = 1
currentRecord.totalQuantity = currentRecord.count
tinsert(self.compactRecords, currentRecord)
else
currentRecord.numAuctions = currentRecord.numAuctions + 1
currentRecord.totalQuantity = currentRecord.totalQuantity + temp.count
end
end
end
if sortParams then
sort(self.compactRecords, function(a, b)
for _, key in ipairs(sortParams) do
local sortVal = sortHelpers[key](a, b)
if sortVal < 0 then
return isAscending
elseif sortVal > 0 then
return not isAscending
end
end
end)
end
end,
-- removes all records for which shouldFilter(record) returns true
FilterRecords = function(self, shouldFilter)
self.shouldCompact = true
local toRemove = {}
for index, record in ipairs(self.records) do
if shouldFilter(record) then
tinsert(toRemove, index)
end
end
for i=#toRemove, 1, -1 do
self:RemoveRecord(toRemove[i])
end
end,
-- removes a record at the given index
RemoveRecord = function(self, index)
local toRemove = self.records[index]
if not toRemove then return end
self.shouldCompact = true
if self.compactRecords then
for i, record in ipairs(self.compactRecords) do
if record:Equals(toRemove) then
if record.numAuctions > 1 then
record.numAuctions = record.numAuctions - 1
else
tremove(self.compactRecords, i)
end
break
end
end
end
if toRemove:IsPlayer() then
self.playerAuctions = self.playerAuctions - 1
end
tremove(self.records, index)
end,
-- adds up all the counts from all the records
GetTotalItemQuantity = function(self)
local totalQuantity = 0
for _, record in ipairs(self.records) do
totalQuantity = totalQuantity + record.count
end
return totalQuantity
end,
-- counts up the number of items (not auctions) the player has
GetPlayerItemQuantity = function(self)
local totalQuantity = 0
for _, record in ipairs(self.records) do
if record:IsPlayer() then
totalQuantity = totalQuantity + record.count
end
end
return totalQuantity
end,
IsPlayerOnly = function(self)
for _, record in ipairs(self.records) do
if not record:IsPlayer() then
return false
end
end
return true
end,
SetDestroyingNum = function(self, num)
self.destroyingNum = num
end,
}
function TSMAPI.AuctionScan:NewAuctionItem()
local o = {}
setmetatable(o, AuctionItem)
AuctionItem.__index = AuctionItem
o:Initialize()
return o
end
local function CompareStrings(a, b)
if a < b then
return -1
elseif a > b then
return 1
else
return 0
end
end
-- bunch of helper functions for AuctionItem sorting
-- negative return means a < b
-- possitive return means a > b
-- zero return means a == b
sortHelpers = {
Percent = function(a, b)
return (a:GetPercent() or math.huge) - (b:GetPercent() or math.huge)
end,
Buyout = function(a, b)
return (a.buyout or math.huge) - (b.buyout or math.huge)
end,
DisplayedBid = function(a, b)
return a:GetDisplayedBid() - b:GetDisplayedBid()
end,
ItemBuyout = function(a, b)
return (a:GetItemBuyout() or math.huge) - (b:GetItemBuyout() or math.huge)
end,
ItemDisplayedBid = function(a, b)
return a:GetItemDisplayedBid() - b:GetItemDisplayedBid()
end,
Count = function(a, b)
return a.count - b.count
end,
Seller = function(a, b)
return CompareStrings(a.seller, b.seller)
end,
TimeLeft = function(a, b)
return a.timeLeft - b.timeLeft
end,
NumAuctions = function(a, b)
return a.numAuctions - b.numAuctions
end,
Name = function(a, b)
local aName = TSMAPI:GetSafeItemInfo(a.parent.itemLink)
local bName = TSMAPI:GetSafeItemInfo(a.parent.itemLink)
return CompareStrings(aName, bName)
end,
DestroyingBuyout = function(a, b)
return (a:GetItemDestroyingBuyout() or math.huge) - (b:GetItemDestroyingBuyout() or math.huge)
end,
}
function TSMAPI.AuctionScan:SortAuctions(data, sortParams, useCompactRecords, isAscending)
local function compareSort(a, b)
for _, key in ipairs(sortParams) do
local sortVal
if useCompactRecords then
sortVal = sortHelpers[key](a.compactRecords[1], b.compactRecords[1])
else
sortVal = sortHelpers[key](a.records[1], b.records[1])
end
if sortVal < 0 then
return isAscending
elseif sortVal > 0 then
return not isAscending
end
end
end
sort(data, compareSort)
end

314
TradeSkillMaster/Auction/AuctionQueryUtil.lua

@ -0,0 +1,314 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for scanning the auction house
local TSM = select(2, ...)
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionQueryUtil_private")
local ITEM_CLASS_LOOKUP = {}
for i, class in ipairs({GetAuctionItemClasses()}) do
ITEM_CLASS_LOOKUP[class] = {}
ITEM_CLASS_LOOKUP[class].index = i
for j, subclass in pairs({GetAuctionItemSubClasses(i)}) do
ITEM_CLASS_LOOKUP[class][subclass] = j
end
end
local function GetItemClasses(itemString)
local class, subClass = select(6, TSMAPI:GetSafeItemInfo(itemString))
if not class or not ITEM_CLASS_LOOKUP[class] then return end
return ITEM_CLASS_LOOKUP[class].index, ITEM_CLASS_LOOKUP[class][subClass]
end
function TSMAPI:GetAuctionQueryInfo(itemString)
local name, _, rarity, _, minLevel, class, subClass, _, equipLoc = TSMAPI:GetSafeItemInfo(itemString)
local class, subClass = GetItemClasses(itemString)
if not name then return end
return {name=name, minLevel=minLevel, maxLevel=minLevel, invType=0, class=class, subClass=subClass, quality=rarity}
end
local function GetCommonQueryInfo(name, items)
local queries = {}
for _, itemString in ipairs(items) do
local itemQuery = TSMAPI:GetAuctionQueryInfo(itemString)
local existingQuery
for _, query in ipairs(queries) do
if query.class == itemQuery.class then
existingQuery = query
break
end
end
if existingQuery then
existingQuery.minLevel = min(existingQuery.minLevel, itemQuery.minLevel)
existingQuery.maxLevel = max(existingQuery.maxLevel, itemQuery.maxLevel)
existingQuery.quality = min(existingQuery.quality, itemQuery.quality)
if existingQuery.subClass ~= itemQuery.subClass then
existingQuery.subClass = nil
end
tinsert(existingQuery.items, itemString)
else
itemQuery.name = name
itemQuery.items = {itemString}
tinsert(queries, itemQuery)
end
end
return queries
end
local function GetCommonQueryInfoClass(class, items)
local resultQuery = TSMAPI:GetAuctionQueryInfo(items[1])
resultQuery.name = ""
resultQuery.class = class
for i=2, #items do
local itemQuery = TSMAPI:GetAuctionQueryInfo(items[i])
resultQuery.minLevel = min(resultQuery.minLevel, itemQuery.minLevel)
resultQuery.maxLevel = max(resultQuery.maxLevel, itemQuery.maxLevel)
resultQuery.quality = min(resultQuery.quality, itemQuery.quality)
if resultQuery.subClass ~= itemQuery.subClass then resultQuery.subClass = nil end
end
resultQuery.items = items
return {resultQuery}
end
local function GreatestSubstring(str1, str2)
local parts1 = {(" "):split(str1)}
local parts2 = {(" "):split(str2)}
for i=1, #parts1 do
if parts1[i] ~= parts2[i] then
local subStr = table.concat(parts1, " ", 1, i-1)
return subStr ~= "" and subStr
end
end
return table.concat(parts1, " ")
end
local function ReduceStrings(strList)
local didReduction = true
while didReduction do
didReduction = false
for i=1, #strList-1 do
if i > #strList-1 then break end
local subStr = GreatestSubstring(strList[i], strList[i+1])
if subStr then
strList[i] = subStr
tremove(strList, i+1)
didReduction = true
end
end
if not private.thread then return end
private.thread:Yield()
end
return true
end
local function NumPagesCallback(event, numPages)
if event == "NUM_PAGES" then
local skippedItems = {}
local score = max(#private.combinedQueries[1].items-numPages, 0)
if private.combinedQueries[1].name == "" then
-- This is a common class term so determine if we should use this or not.
local cost = 0
for _, query in ipairs(private.queries) do
if query.score and query.class == private.combinedQueries[1].class then
cost = cost + query.score
end
end
if score >= cost and score > 0 then
-- use the common class term
for i=#private.queries, 1, -1 do
local query = private.queries[i]
local shouldRemove = (query.class == private.combinedQueries[1].class)
if shouldRemove then
tremove(private.queries, i)
end
end
tinsert(private.queries, private.combinedQueries[1])
end
else
if numPages > #private.combinedQueries[1].items then
for _, itemString in ipairs(private.combinedQueries[1].items) do
local query = TSMAPI:GetAuctionQueryInfo(itemString)
query.items = {itemString}
query.score = 0
tinsert(private.queries, query)
end
elseif numPages == 0 then
for _, itemString in ipairs(private.combinedQueries[1].items) do
tinsert(skippedItems, itemString)
end
else
-- use the common search term
private.combinedQueries[1].score = score
tinsert(private.queries, private.combinedQueries[1])
end
end
tremove(private.combinedQueries, 1)
private.callback("QUERY_UPDATE", private.totalQueries-#private.combinedQueries, private.totalQueries, skippedItems)
end
private:CheckNextCombinedQuery()
end
function private:CheckNextCombinedQuery()
if not private.isScanning then return end
if #private.combinedQueries == 0 then
-- we're done
sort(private.queries, function(a, b) return a.name < b.name end)
TSM:StopGeneratingQueries()
TSMAPI:CreateTimeDelay("queryUtilCallbackDelay", 0.05, function() private.callback("QUERY_COMPLETE", private.queries) end)
return
end
for _, itemString in ipairs(private.combinedQueries[1].items) do
if strlower(private.combinedQueries[1].name) == strlower(TSMAPI:GetSafeItemInfo(itemString)) then
-- One of the items in this combined query is the same as the common search term,
-- so it's always worth using this common search term.
NumPagesCallback("NUM_PAGES", 1)
return
end
end
TSMAPI.AuctionScan:GetNumPages(private.combinedQueries[1], NumPagesCallback)
end
local function GenerateSearchTerms(names, itemList, isReversed)
sort(names)
if not ReduceStrings(names) then return end -- run the reduction
-- create a table associating all the reduced names to a list of items
local temp = {}
for i, filterName in ipairs(names) do
for j, itemString in ipairs(itemList) do
local itemName = TSMAPI:GetSafeItemInfo(itemString)
itemName = itemName and isReversed and strrev(itemName) or itemName -- reverse item name if necessary
if itemName and strfind(itemName, "^"..TSMAPI:StrEscape(filterName)) then
temp[filterName] = temp[filterName] or {}
tinsert(temp[filterName], itemString)
end
end
if not private.thread then return end
private.thread:Yield()
end
return temp
end
local function GenerateQueriesThread(self)
private.thread = self
local function GenerateFilters(reverse)
-- create a list of all item names
local names = {}
for _, itemString in ipairs(private.itemList) do
local name = TSMAPI:GetSafeItemInfo(itemString)
if type(name) == "string" and name ~= "" then
tinsert(names, reverse and strrev(name) or name)
end
end
if not private.thread then return end
local filters, tempFilters, tempItems = {}, {}, {}
local numFilters = 0
local tbl = GenerateSearchTerms(names, private.itemList, reverse)
if not tbl then return end
for filterName, items in pairs(tbl) do
if #items > 1 then
filters[reverse and strrev(filterName) or filterName] = items
numFilters = numFilters + 1
else
tinsert(tempFilters, strrev(filterName)) -- reverse name for second pass
for _, itemString in ipairs(items) do
tinsert(tempItems, itemString)
end
end
end
-- try to find common search terms of reversed item names
local tbl = GenerateSearchTerms(tempFilters, tempItems, not reverse)
if not tbl then return end
for filterName, items in pairs(tbl) do
filters[reverse and filterName or strrev(filterName)] = items
numFilters = numFilters + 1
end
return filters, numFilters
end
local endTime = debugprofilestop() + 5000
while debugprofilestop() < endTime do
-- request all the item info
local tryAgain = false
for _, itemString in ipairs(private.itemList) do
if not TSMAPI:GetSafeItemInfo(itemString) then
tryAgain = true
end
end
if not tryAgain then break end
self:Sleep(0.1)
end
local filters1, num1 = GenerateFilters()
local filters2, num2 = GenerateFilters(true)
if not filters1 or not filters2 then return end
local filters = num2 < num1 and filters2 or filters1
-- generate class filters
local itemClasses = {}
local classes = {GetAuctionItemClasses()}
for _, itemString in ipairs(private.itemList) do
local classIndex = GetItemClasses(itemString)
if classIndex then
itemClasses[classIndex] = itemClasses[classIndex] or {}
tinsert(itemClasses[classIndex], itemString)
end
end
-- create the actual queries
local queries, combinedQueries = {}, {}
for filterName, items in pairs(filters) do
for _, query in ipairs(GetCommonQueryInfo(filterName, items)) do
if #query.items > 1 then
tinsert(combinedQueries, query)
else
tinsert(queries, query)
end
end
end
for class, items in pairs(itemClasses) do
for _, query in ipairs(GetCommonQueryInfoClass(class, items)) do
if #query.items > 1 then
tinsert(combinedQueries, query)
end
end
end
private.isScanning = true
private.queries = queries
private.combinedQueries = combinedQueries
private.totalQueries = #combinedQueries
end
function TSMAPI:GenerateQueries(itemList, callback)
if private.thread then return end
private.itemList = itemList
private.callback = callback
local function ThreadDone()
if private.thread then
private.thread = nil
private:CheckNextCombinedQuery()
end
end
TSMAPI.Threading:Start(GenerateQueriesThread, 0.5, ThreadDone)
end
function TSM:StopGeneratingQueries()
private.thread = nil
private.isScanning = nil
end

730
TradeSkillMaster/Auction/AuctionResultsTable.lua

@ -0,0 +1,730 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local RT_COUNT = 1
local HEAD_HEIGHT = 27
local HEAD_SPACE = 2
local purchaseCache = {}
local resultsTables = {}
local function OnSizeChanged(rt, width)
for i, col in ipairs(rt.headCols) do
col:SetWidth(col.info.width*width)
end
for _, row in ipairs(rt.rows) do
for i, col in ipairs(row.cols) do
col:SetWidth(rt.headCols[i].info.width*width)
end
end
end
local rowTextFunctions = {
GetPriceText = function(buyout, displayBid)
local bidLine = TSMAPI:FormatTextMoney(displayBid, "|cff999999", true) or "|cff999999---|r"
local buyoutLine = buyout and buyout > 0 and TSMAPI:FormatTextMoney(buyout, nil, true) or "---"
if TSM.db.profile.showBids then
return bidLine.."\n"..buyoutLine
else
return buyoutLine
end
end,
GetTimeLeftText = function(timeLeft)
return _G["AUCTION_TIME_LEFT"..(timeLeft or "")] or ""
end,
GetNameText = function(_, link)
return gsub(gsub(link, "%[", ""), "%]", "")
end,
GetAuctionsText = function(num, player, isExpandable, totalNum)
num = totalNum or num
local playerText = player and (" |cffffff00("..player..")|r") or ""
if isExpandable then
return TSMAPI.Design:GetInlineColor("link2")..num.."|r"..playerText
else
return num..playerText
end
end,
GetSellerText = function(seller)
if TSMAPI:IsPlayer(seller) then
return "|cffffff00"..seller.."|r"
else
return seller or ""
end
end,
GetPercentText = function(pct)
if not pct then return "---" end
return TSMAPI:GetAuctionPercentColor(pct)..floor(pct+0.5).."%|r"
end
}
local function GetRowTable(rt, auction, isExpandable)
if not auction then return end
local bid, buyout
if TSM.db.profile.pricePerUnit then
bid = auction:GetItemDisplayedBid()
buyout = auction:GetItemBuyout()
else
bid = auction:GetDisplayedBid()
buyout = auction.buyout
end
local auctionsData, rowTable
local itemString = auction.parent:GetItemString()
if rt.expanded[itemString] then
auctionsData = {#auction.parent.records, nil, nil, auction.numAuctions}
else
auctionsData = {#auction.parent.records, auction.parent.records[1].playerAuctions, isExpandable}
end
local name, iLvl
-- if strmatch(auction.parent.itemLink, "battlepet") then
-- local _, speciesID, itemLvl = strsplit(":", auction.parent.itemLink)
-- local itemName = C_PetJournal.GetPetInfoBySpeciesID(speciesID)
-- name, iLvl = itemName, itemLvl
-- else
local itemName, _, _, itemLvl = GetItemInfo(auction.parent.itemLink)
name, iLvl = itemName, itemLvl
-- end
local pct = auction:GetPercent()
if not pct or pct < 0 or pct == math.huge then
pct = nil
end
if rt.isDestroying then
local destroyingBid = auction:GetItemDestroyingDisplayedBid()
local destroyingBuyout = auction:GetItemDestroyingBuyout()
rowTable = {
{value=rowTextFunctions.GetNameText, args={name, auction.parent.itemLink}},
{value=rowTextFunctions.GetAuctionsText, args=auctionsData},
{value=auction.count, args={auction.count}},
{value=rowTextFunctions.GetSellerText, args={auction.seller}},
{value=rowTextFunctions.GetPriceText, args={destroyingBuyout, destroyingBid}},
{value=rowTextFunctions.GetPriceText, args={buyout, bid}},
{value=rowTextFunctions.GetPercentText, args={pct}},
}
else
rowTable = {
{value=rowTextFunctions.GetNameText, args={name, auction.parent.itemLink}},
{value=(iLvl or "---"), args={iLvl or 0}},
{value=rowTextFunctions.GetAuctionsText, args=auctionsData},
{value=auction.count, args={auction.count}},
{value=rowTextFunctions.GetTimeLeftText, args={auction.timeLeft}},
{value=rowTextFunctions.GetSellerText, args={auction.seller}},
{value=rowTextFunctions.GetPriceText, args={buyout, bid}},
{value=rowTextFunctions.GetPercentText, args={pct}},
}
end
rowTable.itemString = itemString
rowTable.auctionRecord = auction
rowTable.expandable = isExpandable
rowTable.texture = auction.parent:GetTexture()
rowTable.link = auction.parent.itemLink
return rowTable
end
local function GetTableIndex(tbl, value)
for i, v in pairs(tbl) do
if value == v then
return i
end
end
end
local function OnColumnClick(self, ...)
local button = ...
local rt = self.rt
local column = GetTableIndex(rt.headCols, self)
if button == "RightButton" and column == #rt.headCols-1 then
TSM.db.profile.pricePerUnit = not TSM.db.profile.pricePerUnit
local priceColName = TSM.db.profile.pricePerUnit and L["Price Per Item"] or L["Price Per Stack"]
self:SetText(priceColName)
rt:RefreshRowData()
return
end
local ascending = rt.sortInfo.ascending
rt:SetSort(column, rt.sortInfo.column ~= column or not ascending)
local handler = self.rt.handlers.OnColumnClick
if handler then
handler(self.rt, self.row.data, self, ...)
end
end
local methods = {
DrawRows = function(rt)
if not rt.auctionData then return end
for i=1, rt.NUM_ROWS do
rt.rows[i]:Hide()
end
wipe(rt.displayRows)
local itemsUsed = {}
for i, data in ipairs(rt.data) do
local itemString = data.itemString
if not itemsUsed[itemString] or rt.expanded[itemString] then
tinsert(rt.displayRows, data)
itemsUsed[itemString] = true
elseif i == rt.selected then
rt.selected = nil
end
end
FauxScrollFrame_Update(rt.scrollFrame, #rt.displayRows, rt.NUM_ROWS, rt.ROW_HEIGHT)
local offset = FauxScrollFrame_GetOffset(rt.scrollFrame)
rt.offset = offset
for i=1, min(rt.NUM_ROWS, #rt.displayRows) do
rt.rows[i]:Show()
local data = rt.displayRows[i+offset]
local cols = rt.rows[i].cols
rt.rows[i].data = data
if rt.selected == GetTableIndex(rt.data, data) then
rt.rows[i].highlight:Show()
else
rt.rows[i].highlight:Hide()
end
for j, col in ipairs(rt.rows[i].cols) do
local colData = data[j]
if j == 1 then
col.icon:SetTexture(data.texture)
if data.indented then
col.spacer:SetWidth(10)
col.icon:SetAlpha(0.5)
col:GetFontString():SetAlpha(0.7)
else
col.spacer:SetWidth(1)
col.icon:SetAlpha(1)
col:GetFontString():SetAlpha(1)
end
end
if type(colData.value) == "function" then
col:SetText(colData.value(unpack(colData.args)))
else
col:SetText(colData.value)
end
end
end
rt:UpdateActiveRows()
end,
RefreshRowData = function(rt)
if not rt.auctionData then return end
wipe(rt.data)
wipe(rt.displayRows)
local function RowSort(a, b)
if a[rt.sortInfo.column].args[1] == nil then return end
local aVal
local bVal
if getn(a[rt.sortInfo.column].args) == 4 then
aVal = a[rt.sortInfo.column].args[4]
bVal = b[rt.sortInfo.column].args[4]
else
aVal = a[rt.sortInfo.column].args[1]
bVal = b[rt.sortInfo.column].args[1]
end
-- local aVal = a[rt.sortInfo.column].args[1]
-- local bVal = b[rt.sortInfo.column].args[1]
if type(aVal) ~= "string" or type(bVal) ~= "string" then
aVal = tonumber(aVal) or 0
bVal = tonumber(bVal) or 0
end
if aVal == bVal then
-- make this a stable sort (abitrarily) by using table reference strings
return tostring(a) < tostring(b)
end
if rt.sortInfo.ascending then
return aVal < bVal
else
return aVal > bVal
end
end
local tmp = {}
for _, auction in ipairs(rt.auctionData) do
local itemString = auction:GetItemString()
local itemRowData = {}
for i, data in ipairs(auction.compactRecords) do
local rowTbl = GetRowTable(rt, data, #auction.compactRecords > 1)
rowTbl.indented = true
tinsert(itemRowData, rowTbl)
end
sort(itemRowData, RowSort)
if itemRowData[1] then
itemRowData[1].indented = false
end
tinsert(tmp, itemRowData)
end
sort(tmp, function(a, b) return RowSort(a[1], b[1]) end)
for _, itemRows in ipairs(tmp) do
for _, row in ipairs(itemRows) do
tinsert(rt.data, row)
end
end
rt:DrawRows()
end,
SetData = function(rt, auctionData)
rt.auctionData = auctionData
rt:RefreshRowData()
end,
ClearSelection = function(rt)
rt.selected = nil
rt:DrawRows()
end,
SetSelectedAuction = function(rt, auction)
rt.selected = nil
for i, data in ipairs(rt.data) do
if type(auction) == "table" then
if data.auctionRecord == auction or data.auctionRecord:Equals(auction) then
rt.selected = i
break
end
elseif type(auction) == "string" then
if data.itemString == auction then
rt.selected = i
break
end
end
end
rt:DrawRows()
end,
GetSelectedAuction = function(rt)
if not rt.selected or not rt.data[rt.selected] then return end
return rt.data[rt.selected].auctionRecord
end,
SetExpanded = function(rt, itemString, expanded)
rt.expanded[itemString] = expanded
rt:RefreshRowData()
end,
ToggleExpanded = function(rt, itemString)
rt.expanded[itemString] = not rt.expanded[itemString]
rt:RefreshRowData()
end,
SetSort = function(rt, column, ascending)
if not rt.headCols[column or 0] then return end
rt.sortInfo.column = column
rt.sortInfo.ascending = ascending
for _, col in ipairs(rt.headCols) do
local tex = col:GetNormalTexture()
tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight")
tex:SetTexCoord(0.017, 1, 0.083, 0.909)
tex:SetAlpha(0.5)
end
if ascending then
rt.headCols[column]:GetNormalTexture():SetTexture(0.6, 0.8, 1, 0.8)
else
rt.headCols[column]:GetNormalTexture():SetTexture(0.8, 0.6, 1, 0.8)
end
rt:RefreshRowData()
end,
SetDisabled = function(rt, disabled)
rt.disabled = disabled
end,
SetColHeadText = function(rt, column, text)
rt.headCols[column]:SetText(text)
end,
UpdateActiveRows = function(rt)
if not rt.quickBuyout then return end
for _, row in ipairs(rt.rows) do
row:HideActiveBorder()
if row.data then
local rowRecord = row.data.auctionRecord
for i=1, GetNumAuctionItems("list") do
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", i))
-- local _, _, count, _, _, _, _, _, _, buyout, _, _, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, _, _, buyout, _, _, seller = GetAuctionItemInfo("list", i)
if itemString == row.data.itemString and rowRecord.count == count and rowRecord.buyout == buyout and rowRecord.seller == seller then
row:ShowActiveBorder()
break
end
end
end
end
wipe(purchaseCache)
end,
}
local defaultColScripts = {
OnEnter = function(self, ...)
if self.rt.disabled then return end
if self ~= self.row.cols[1] or not self.rt.isShowingItemTooltip then
GameTooltip:SetOwner(self, "ANCHOR_NONE")
GameTooltip:SetPoint("BOTTOMLEFT", self, "TOPLEFT")
local data = self.row.data
local extra = ""
if self.row.isActive then
extra = TSMAPI.Design:GetInlineColor("link").."\n\n"..L["Alt-Click to immediately buyout this auction."].."|r"
end
if self.rt.expanded[data.itemString] then
GameTooltip:AddLine(L["Double-click to collapse this item and show only the cheapest auction."]..extra, 1, 1, 1, true)
elseif data.expandable then
GameTooltip:AddLine(L["Double-click to expand this item and show all the auctions."]..extra, 1, 1, 1, true)
else
GameTooltip:AddLine(L["There is only one price level and seller for this item."]..extra, 1, 1, 1, true)
end
GameTooltip:Show()
end
self.row.highlight:Show()
local handler = self.rt.handlers.OnEnter
if handler then
handler(self.rt, self.row.data, self, ...)
end
end,
OnLeave = function(self, ...)
if self.rt.disabled then return end
if self ~= self.row.cols[1] or not self.rt.isShowingItemTooltip then
GameTooltip:Hide()
end
if not self.rt.selected or self.rt.selected ~= GetTableIndex(self.rt.data, self.row.data) then
self.row.highlight:Hide()
end
local handler = self.rt.handlers.OnLeave
if handler then
handler(self.rt, self.row.data, self, ...)
end
end,
OnClick = function(self, button, ...)
if self.rt.disabled then return end
self.rt:ClearSelection()
self.rt.selected = GetTableIndex(self.rt.data, self.row.data)
self.row.highlight:Show()
if self.rt.quickBuyout and IsAltKeyDown() then
local rowRecord = self.row.data.auctionRecord
for i=GetNumAuctionItems("list"), 1, -1 do
local link = GetAuctionItemLink("list", i)
if not purchaseCache[link] then
local itemString = TSMAPI:GetItemString(link)
-- local _, _, count, _, _, _, _, _, _, buyout, _, _, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, _, _, buyout, _, _, seller = GetAuctionItemInfo("list", i)
if itemString == self.row.data.itemString and rowRecord.count == count and rowRecord.buyout == buyout and rowRecord.seller == seller then
PlaceAuctionBid("list", i, rowRecord.buyout)
TSM:AuctionControlCallback("OnBuyout", {itemString=TSMAPI:GetItemString(rowRecord.parent.itemLink), link=rowRecord.parent.itemLink, count=rowRecord.count, seller=rowRecord.seller, buyout=rowRecord.buyout, destroyingNum=rowRecord.parent.destroyingNum})
purchaseCache[link] = true
return
end
end
end
end
local handler = self.rt.handlers.OnClick
if handler then
handler(self.rt, self.row.data, self, button, ...)
end
end,
OnDoubleClick = function(self, ...)
if self.rt.disabled then return end
local data = self.row.data
if data.expandable then
self.rt:ToggleExpanded(data.itemString)
end
local handler = self.rt.handlers.OnDoubleClick
if handler then
handler(self.rt, self.row.data, self, ...)
end
end,
}
function TSMAPI:CreateAuctionResultsTable(parent, handlers, quickBuyout, isDestroying)
local priceColName = TSM.db.profile.pricePerUnit and "Price Per Item" or "Price Per Stack"
local colInfo = isDestroying and {
{name=L["Item"], width=0.43},
{name=L["Auctions"], width=0.07, align="CENTER"},
{name=L["Stack Size"], width=0.05, align="CENTER"},
{name=L["Seller"], width=0.11, align="CENTER"},
{name=L["Price Per Target Item"], width=0.13, align="RIGHT", isPrice=true},
{name=priceColName, width=0.13, align="RIGHT", isPrice=true},
{name=L["% Market Value"], width=0.08, align="CENTER"},
} or {
{name=L["Item"], width=0.42},
{name=L["Item Level"], width=0.05, align="CENTER"},
{name=L["Auctions"], width=0.07, align="CENTER"},
{name=L["Stack Size"], width=0.05, align="CENTER"},
{name=L["Time Left"], width=0.09, align="CENTER"},
{name=L["Seller"], width=0.11, align="CENTER"},
{name=priceColName, width=0.13, align="RIGHT", isPrice=true},
{name=L["% Market Value"], width=0.08, align="CENTER"},
}
local rtName = "TSMAuctionResultsTable"..RT_COUNT
RT_COUNT = RT_COUNT + 1
local rt = CreateFrame("Frame", rtName, parent)
rt.NUM_ROWS = TSM.db.profile.auctionResultRows
rt.ROW_HEIGHT = (parent:GetHeight()-HEAD_HEIGHT-HEAD_SPACE)/rt.NUM_ROWS
rt:SetScript("OnShow", function()
local priceColName = TSM.db.profile.pricePerUnit and L["Price Per Item"] or L["Price Per Stack"]
rt:SetColHeadText(#rt.headCols-1, priceColName)
rt:RefreshRowData()
end)
local contentFrame = CreateFrame("Frame", rtName.."Content", rt)
contentFrame:SetPoint("TOPLEFT")
contentFrame:SetPoint("BOTTOMRIGHT", -15, 0)
contentFrame:SetScript("OnSizeChanged", function(_, width) OnSizeChanged(rt, width) end)
rt.contentFrame = contentFrame
-- frame to hold the header columns and the rows
local scrollFrame = CreateFrame("ScrollFrame", rtName.."ScrollFrame", rt, "FauxScrollFrameTemplate")
scrollFrame:SetScript("OnVerticalScroll", function(self, offset)
FauxScrollFrame_OnVerticalScroll(self, offset, rt.ROW_HEIGHT, function() rt:DrawRows() end)
end)
scrollFrame:SetAllPoints(contentFrame)
rt.scrollFrame = scrollFrame
-- make the scroll bar consistent with the TSM theme
local scrollBar = _G[scrollFrame:GetName().."ScrollBar"]
scrollBar:ClearAllPoints()
scrollBar:SetPoint("BOTTOMRIGHT", rt, -1, 1)
scrollBar:SetPoint("TOPRIGHT", rt, -1, -HEAD_HEIGHT)
scrollBar:SetWidth(12)
local thumbTex = scrollBar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetFrameColor(thumbTex)
thumbTex:SetHeight(150)
thumbTex:SetWidth(scrollBar:GetWidth())
_G[scrollBar:GetName().."ScrollUpButton"]:Hide()
_G[scrollBar:GetName().."ScrollDownButton"]:Hide()
-- create the header columns
rt.headCols = {}
for i, info in ipairs(colInfo) do
local col = CreateFrame("Button", rtName.."HeadCol"..i, rt.contentFrame)
col:SetHeight(HEAD_HEIGHT)
if i == 1 then
col:SetPoint("TOPLEFT")
else
col:SetPoint("TOPLEFT", rt.headCols[i-1], "TOPRIGHT")
end
col.info = info
col.rt = rt
col:RegisterForClicks("AnyUp")
col:SetScript("OnClick", OnColumnClick)
local text = col:CreateFontString()
text:SetJustifyH("CENTER")
text:SetJustifyV("CENTER")
text:SetFont(TSMAPI.Design:GetContentFont("small"))
TSMAPI.Design:SetWidgetTextColor(text)
col:SetFontString(text)
col:SetText(info.name or "")
text:SetAllPoints()
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight")
tex:SetTexCoord(0.017, 1, 0.083, 0.909)
tex:SetAlpha(0.5)
col:SetNormalTexture(tex)
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight")
tex:SetTexCoord(0.025, 0.957, 0.087, 0.931)
tex:SetAlpha(0.2)
col:SetHighlightTexture(tex)
tinsert(rt.headCols, col)
end
-- create the rows
rt.rows = {}
for i=1, rt.NUM_ROWS do
local row = CreateFrame("Frame", rtName.."Row"..i, rt.contentFrame)
row:SetHeight(rt.ROW_HEIGHT)
if i == 1 then
row:SetPoint("TOPLEFT", 0, -(HEAD_HEIGHT+HEAD_SPACE))
row:SetPoint("TOPRIGHT", 0, -(HEAD_HEIGHT+HEAD_SPACE))
else
row:SetPoint("TOPLEFT", rt.rows[i-1], "BOTTOMLEFT")
row:SetPoint("TOPRIGHT", rt.rows[i-1], "BOTTOMRIGHT")
end
local highlight = row:CreateTexture()
highlight:SetAllPoints()
highlight:SetTexture(1, .9, 0, .5)
highlight:Hide()
row.highlight = highlight
row.rt = rt
row.cols = {}
for j=1, #colInfo do
local col = CreateFrame("Button", nil, row)
local text = col:CreateFontString()
if TSM.db.profile.showBids and colInfo[j].isPrice then
text:SetFont(TSMAPI.Design:GetContentFont(), min(13, rt.ROW_HEIGHT/2 - 2))
else
text:SetFont(TSMAPI.Design:GetContentFont(), min(14, rt.ROW_HEIGHT))
end
text:SetJustifyH(colInfo[j].align or "LEFT")
text:SetJustifyV("CENTER")
text:SetPoint("TOPLEFT", 1, -1)
text:SetPoint("BOTTOMRIGHT", -1, 1)
col:SetFontString(text)
col:SetHeight(rt.ROW_HEIGHT)
col:RegisterForClicks("AnyUp")
for name, func in pairs(defaultColScripts) do
col:SetScript(name, func)
end
col.rt = rt
col.row = row
col.rowNum = i
if j == 1 then
col:SetPoint("TOPLEFT")
else
col:SetPoint("TOPLEFT", row.cols[j-1], "TOPRIGHT")
end
if j%2 == 1 then
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture(1, 1, 1, .03)
col:SetNormalTexture(tex)
end
-- special first column to hold spacer / item name / item icon
if j == 1 then
local spacer = CreateFrame("Frame", nil, col)
spacer:SetPoint("TOPLEFT")
spacer:SetHeight(rt.ROW_HEIGHT)
spacer:SetWidth(1)
col.spacer = spacer
local iconBtn = CreateFrame("Button", nil, col)
iconBtn:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1.5})
iconBtn:SetBackdropBorderColor(0, 1, 0, 0)
iconBtn:SetPoint("TOPLEFT", spacer, "TOPRIGHT")
iconBtn:SetHeight(rt.ROW_HEIGHT)
iconBtn:SetWidth(rt.ROW_HEIGHT)
iconBtn:SetScript("OnEnter", function(self)
if row.data.link then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
TSMAPI:SafeTooltipLink(row.data.link)
GameTooltip:Show()
rt.isShowingItemTooltip = true
end
end)
iconBtn:SetScript("OnLeave", function(self)
-- BattlePetTooltip:Hide()
GameTooltip:ClearLines()
GameTooltip:Hide()
rt.isShowingItemTooltip = false
end)
iconBtn:SetScript("OnClick", function(_, ...)
if IsModifiedClick() then
HandleModifiedItemClick(row.data.auctionRecord.parent.itemLink)
else
col:GetScript("OnClick")(col, ...)
end
end)
iconBtn:SetScript("OnDoubleClick", function(_, ...)
col:GetScript("OnDoubleClick")(col, ...)
end)
local icon = iconBtn:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", 2, -2)
icon:SetPoint("BOTTOMRIGHT", -2, 2)
col.iconBtn = iconBtn
col.icon = icon
row.ShowActiveBorder = function()
if rt.quickBuyout then
row.isActive = true
iconBtn:SetBackdropBorderColor(0, 1, 0, .7)
end
end
row.HideActiveBorder = function()
row.isActive = nil
iconBtn:SetBackdropBorderColor(0, 0, 0, 0)
end
text:ClearAllPoints()
text:SetPoint("TOPLEFT", iconBtn, "TOPRIGHT", 2, 0)
text:SetPoint("BOTTOMRIGHT")
end
tinsert(row.cols, col)
end
if i%2 == 0 then
local tex = row:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight")
tex:SetTexCoord(0.017, 1, 0.083, 0.909)
tex:SetAlpha(0.3)
end
tinsert(rt.rows, row)
end
rt:SetAllPoints()
rt.data = {}
rt.expanded = {}
rt.displayRows = {}
rt.handlers = handlers or {}
rt.sortInfo = {}
rt.quickBuyout = quickBuyout
rt.isDestroying = isDestroying
tinsert(resultsTables, rt)
for name, func in pairs(methods) do
rt[name] = func
end
LibStub("AceEvent-3.0").RegisterEvent(rt, "AUCTION_ITEM_LIST_UPDATE", "UpdateActiveRows")
return rt
end

628
TradeSkillMaster/Auction/AuctionScanning.lua

@ -0,0 +1,628 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for scanning the auction house
local TSM = select(2, ...)
local AuctionScanning = TSM:NewModule("AuctionScanning", "AceEvent-3.0")
TSMAPI.AuctionScan = {}
local RETRY_DELAY = 2
local MAX_RETRIES = 4
local BASE_DELAY = 0.10 -- time to delay for before trying to scan a page again when it isn't fully loaded
local private = { callbackHandler = nil, query = {}, options = {}, data = {}, isScanning = nil }
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionScanning_private")
local scanCache = {}
local CACHE_DECAY_PER_DAY = 5
local CACHE_AUTO_HIT_TIME = 10 * 60
local SECONDS_PER_DAY = 60 * 60 * 24
local function DoCallback(...)
if type(private.callbackHandler) == "function" then
private.callbackHandler(...)
end
end
local function eventHandler(event)
if event == "AUCTION_HOUSE_CLOSED" then
-- auction house was closed, make sure all scanning is stopped
AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
private.auctionHouseShown = false
DoCallback("INTERRUPTED")
private:StopScanning()
elseif event == "AUCTION_ITEM_LIST_UPDATE" then
-- gets called whenever the AH window is updated (something is shown in the results section)
AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
TSMAPI:CancelFrame("updateDelay")
-- now that our query was successful, we can get our data
private:ScanAuctions()
end
end
function AuctionScanning:OnEnable()
AuctionScanning:RegisterEvent("AUCTION_HOUSE_CLOSED", eventHandler)
end
function private:ScanAuctionPage(resolveSellers)
local shown = GetNumAuctionItems("list")
local badData = false
local auctions = {}
for i = 1, shown do
-- checks to make sure all the data has been sent to the client
-- if not, the data is bad and we'll wait / try again
-- local count, _, _, _, _, _, _, buyout, _, _, _, seller = select(3, GetAuctionItemInfo("list", i))
local count, _, _, _, _, _, buyout, _, _, seller = select(3, GetAuctionItemInfo("list", i))
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", i))
auctions[i] = { itemString = itemString, index = i, count = count, buyout = buyout, seller = seller }
if not (itemString and buyout and count and (seller or not resolveSellers or buyout == 0)) then
badData = true
end
end
return badData, auctions
end
function IsDuplicatePage()
if not private.pageTemp or GetNumAuctionItems("list") == 0 then return false end
local numLinks, prevLink = 0, nil
for i = 1, GetNumAuctionItems("list") do
-- local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local link = GetAuctionItemLink("list", i)
local temp = private.pageTemp[i]
if not prevLink then
prevLink = link
elseif prevLink ~= link then
prevLink = link
numLinks = numLinks + 1
end
if not temp or temp.count ~= count or temp.minBid ~= minBid or temp.minInc ~= minInc or temp.buyout ~= buyout or temp.bid ~= bid or temp.seller ~= seller or temp.link ~= link then
return false
end
end
if numLinks > 1 and private.pageTemp.shown == GetNumAuctionItems("list") then
return false
end
return true
end
local function PopulatePageTemp()
local shown = GetNumAuctionItems("list")
private.pageTemp = { numShown = shown }
for i = 1, shown do
-- checks to make sure all the data has been sent to the client
-- if not, the data is bad and we'll wait / try again
-- local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local link = GetAuctionItemLink("list", i)
private.pageTemp[i] = { count = count, minBid = minBid, minInc = minInc, buyout = buyout, bid = bid, seller = seller, link = link }
end
end
-- Starts a scan of the auction house.
-- query - A single query containing QueryAuctionItem paramters:
-- name, minLevel, maxLevel, invType, class, subClass, usable, quality
-- resolveSellers - whether or not to resolve seller names
-- maxPrice - stop scanning when prices go above this price
function TSMAPI.AuctionScan:RunQuery(query, callbackHandler, resolveSellers, maxPrice, doCache)
TSMAPI.AuctionScan:StopScan() -- stop any scan in progress
if not AuctionFrame:IsVisible() then
return -1 -- the auction house isn't open (return code -1)
elseif type(query) ~= "table" then
return -2 -- the scan queue is not a table (return code -2)
elseif not CanSendAuctionQuery() then
TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:RunQuery(query, callbackHandler, resolveSellers, maxPrice, doCache) end)
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
-- sort by buyout
SortAuctionItems("list", "buyout")
if IsAuctionSortReversed("list", "buyout") then
SortAuctionItems("list", "buyout")
end
-- setup the query
private.query = CopyTable(query)
private.query.page = 0 -- the current page of this query we're scanning
private.query.timeDelay = 0 -- a delay used to wait for information to show up
private.query.retries = 0 -- how many times we've done a hard retry so far
private.query.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query
private.cache = doCache and { query = CopyTable(query), items = {} } or nil
-- setup other stuff
wipe(private.data)
private.isScanning = true
private.callbackHandler = callbackHandler
private.resolveSellers = resolveSellers
private.scanType = "query"
private.maxPrice = maxPrice or math.huge
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
function TSMAPI.AuctionScan:ScanLastPage(callbackHandler)
private:StopScanning() -- stop any scan in progress
if not AuctionFrame:IsVisible() then
return -1 -- the auction house isn't open (return code -1)
elseif not CanSendAuctionQuery() then
TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:ScanLastPage(callbackHandler) end)
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
-- clear the auction sort
SortAuctionClearSort("list")
-- setup the query
private.query = {name="", page=0}
private.query.timeDelay = 0 -- a delay used to wait for information to show up
private.query.retries = 0 -- how many times we've done a hard retry so far
private.query.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query
-- setup other stuff
wipe(private.data)
private.isScanning = true
private.callbackHandler = callbackHandler
private.scanType = "lastPage"
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay)
function private:SendQuery()
if not private.isScanning then return end
if CanSendAuctionQuery() then
-- stop delay timer
TSMAPI:CancelFrame("queryDelay")
-- Query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire)
AuctionScanning:RegisterEvent("AUCTION_ITEM_LIST_UPDATE", eventHandler)
-- [exact] cardinal ruby 0 0 nil 0 0 0 0 0
-- [normal] cardinal ruby nil nil nil nil nil 0 nil nil
QueryAuctionItems(private.query.name, private.query.minLevel, private.query.maxLevel, private.query.invType, private.query.class, private.query.subClass, private.query.page, private.query.usable, private.query.quality)
else
-- run delay timer then try again to scan
TSMAPI:CreateTimeDelay("queryDelay", 0.05, private.SendQuery)
end
end
--scans the currently shown page of auctions and collects all the data
function private:ScanAuctions()
if not private.isScanning then return end
local shown, total = GetNumAuctionItems("list")
local totalPages = ceil(total / NUM_AUCTION_ITEMS_PER_PAGE)
if private.scanType == "numPages" then
local cacheData = TSM.db.factionrealm.numPagesCache[private.query.cacheKey]
cacheData.lastScan = time()
local confidence = (120 - cacheData.confidence) / (CACHE_DECAY_PER_DAY * 2)
local diff = abs(cacheData.avg - totalPages)
if diff <= 1 and diff > 0.5 then
confidence = floor(confidence * (1.5 - diff))
elseif diff > 1 then
confidence = floor(confidence - CACHE_DECAY_PER_DAY * diff)
end
cacheData.confidence = max(floor(cacheData.confidence + confidence), 0)
cacheData.avg = (cacheData.avg * cacheData.numScans + totalPages) / (cacheData.numScans + 1)
cacheData.numScans = cacheData.numScans + 1
private:StopScanning()
return DoCallback("NUM_PAGES", totalPages)
elseif private.scanType == "lastPage" then
local lastPage = floor(total / NUM_AUCTION_ITEMS_PER_PAGE)
if private.query.page ~= lastPage then
private.query.page = lastPage
return private:SendQuery()
end
end
local dataIsBad, auctions = private:ScanAuctionPage(private.resolveSellers)
-- check that we have good data
if dataIsBad or IsDuplicatePage() then
if private.query.retries < MAX_RETRIES then
if private.query.hardRetry then
-- Hard retry
-- re-sends the entire query
private.query.retries = private.query.retries + 1
private.query.timeDelay = 0
private.query.hardRetry = nil
private:SendQuery()
else
-- Soft retry
-- runs a delay and then tries to scan the query again
private.query.timeDelay = private.query.timeDelay + BASE_DELAY
TSMAPI:CreateTimeDelay("updateDelay", BASE_DELAY, private.ScanAuctions)
-- If after 2 seconds of retrying we still don't have data, will go and requery to try and solve the issue
-- if we still don't have data, we try to scan it anyway and move on.
if private.query.timeDelay >= RETRY_DELAY then
private.query.hardRetry = true
end
end
return
end
end
if private.cache then
-- store info in cache
for i, v in ipairs(auctions) do
local cacheTmp = CopyTable(v)
cacheTmp.index = private.query.page * 50 + i
tinsert(private.cache, cacheTmp)
private.cache.items[cacheTmp.itemString] = true
end
end
private.query.hardRetry = nil
private.query.retries = 0
private.query.timeDelay = 0
if private.scanType ~= "lastPage" then
private.query.page = private.query.page + 1 -- increment current page
if totalPages > 0 then
DoCallback("SCAN_PAGE_UPDATE", private.query.page, totalPages)
end
end
PopulatePageTemp()
-- now that we know our query is good, time to verify and then store our data
for _, v in ipairs(auctions) do
if private:AddAuctionRecord(v.index) then
-- we've hit the max price so we're done scanning
private:StopScanning()
return DoCallback("SCAN_COMPLETE", private.data)
end
end
if private.scanType == "lastPage" then
return DoCallback("SCAN_LAST_PAGE_COMPLETE", private.data)
elseif private.query.page >= totalPages then
-- we have finished scanning this query
private:StopScanning()
return DoCallback("SCAN_COMPLETE", private.data)
end
-- query the next page and continue scanning
private:SendQuery()
end
-- Add a new record to the private.data table
function private:AddAuctionRecord(index)
-- local name, texture, count, _, _, _, _, minBid, minIncrement, buyout, bid, highBidder, highBidder_full, seller, seller_full = GetAuctionItemInfo("list", index)
local name, texture, count, _, _, _, minBid, minIncrement, buyout, bid, highBidder, seller = GetAuctionItemInfo("list", index)
seller = TSM:GetAuctionPlayer(seller, null)
highBidder = TSM:GetAuctionPlayer(highBidder, null)
local timeLeft = GetAuctionItemTimeLeft("list", index)
local link = GetAuctionItemLink("list", index)
local itemString = TSMAPI:GetItemString(link)
if not itemString then return end
-- Create a new entry in the table
if not private.data[itemString] then
private.data[itemString] = TSMAPI.AuctionScan:NewAuctionItem()
private.data[itemString]:SetItemLink(link)
private.data[itemString]:SetTexture(texture)
end
private.data[itemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or "?", timeLeft)
-- add the base item if necessary
local baseItemString = TSMAPI:GetBaseItemString(itemString)
if baseItemString ~= itemString then
-- Create a new entry in the table
if not private.data[baseItemString] then
private.data[baseItemString] = TSMAPI.AuctionScan:NewAuctionItem()
private.data[baseItemString]:SetItemLink(link)
private.data[baseItemString]:SetTexture(texture)
end
private.data[baseItemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or "?", timeLeft)
private.data[baseItemString].isBaseItem = true
end
if select(8, TSMAPI:GetSafeItemInfo(link)) == count then
return (buyout or 0) / count > (private.maxPrice or math.huge)
end
end
-- stops the scan when we are finished scanning, it was interrupted, or somebody stopped it
function private:StopScanning()
TSMAPI:CancelFrame("cantSendAuctionQueryDelay")
if not private.isScanning then return end
if private.cache then
-- store the cache info
sort(private.cache, function(a, b) return a.index < b.index end)
for itemString in pairs(private.cache.items) do
scanCache[itemString] = private.cache
end
wipe(private.cache.items)
private.cache = nil
end
-- cancel any delays that might still be running
TSMAPI:CancelFrame("queryDelay")
TSMAPI:CancelFrame("updateDelay")
AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
private.isScanning = nil
private.pageTemp = nil
end
-- API for stopping the scan
-- returns true/false if we were/weren't actually scanning
function TSMAPI.AuctionScan:StopScan()
private:StopScanning()
TSM:StopGeneratingQueries()
end
-- Gets the number of pages for a given query
function TSMAPI.AuctionScan:GetNumPages(query, callbackHandler)
private:StopScanning() -- stop any scan in progress
if not AuctionFrame:IsVisible() then
return -1 -- the auction house isn't open (return code -1)
elseif type(query) ~= "table" then
return -2 -- the scan queue is not a table (return code -2)
elseif not CanSendAuctionQuery() then
TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:GetNumPages(query, callbackHandler) end)
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
-- fancy caching
local temp = {}
for i, field in ipairs({ "name", "minLevel", "maxLevel", "invType", "class", "subClass", "usable", "quality" }) do
temp[i] = tostring(query[field])
end
local cacheKey = table.concat(temp, "~")
local cacheData = TSM.db.factionrealm.numPagesCache[cacheKey]
if cacheData then
local cacheHit
if time() - cacheData.lastScan < CACHE_AUTO_HIT_TIME then
-- auto cache hit
cacheHit = true
elseif random(1, 100) <= cacheData.confidence then
-- cache hit
cacheData.confidence = cacheData.confidence - floor(((time() - cacheData.lastScan) / SECONDS_PER_DAY) * CACHE_DECAY_PER_DAY + 0.5)
cacheData.confidence = max(cacheData.confidence, 0) -- ensure >= 0
cacheHit = true
end
if cacheHit then
local numPages = max(ceil(cacheData.avg), 1) -- round avg num of pages up and ensure >= 1
TSMAPI:CreateTimeDelay("numPagesCacheDelay", 0, function() callbackHandler("NUM_PAGES", numPages) end)
return 2
end
else
TSM.db.factionrealm.numPagesCache[cacheKey] = { avg = 0, confidence = 0, numScans = 0, lastScan = 0 }
end
-- setup the query
private.query = CopyTable(query)
private.query.cacheKey = cacheKey
-- setup other stuff
wipe(private.data)
private.isScanning = true
private.callbackHandler = callbackHandler
private.scanType = "numPages"
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
function TSMAPI.AuctionScan:CacheRemove(itemString, index)
if scanCache[itemString] then
tremove(scanCache[itemString], index)
end
end
function TSMAPI.AuctionScan:ClearCache()
wipe(scanCache)
end
local findPrivate = {}
findPrivate.findFrame = findPrivate.findFrame or CreateFrame("Frame")
local function eventHandler(frame, event)
if event == "AUCTION_HOUSE_SHOW" then
-- auction house was opened
elseif event == "AUCTION_HOUSE_CLOSED" then
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if findPrivate.isScanning then -- stop scanning if we were scanning (pass true to specify it was interrupted)
TSMAPI.AuctionScan:StopFindScan()
end
elseif event == "AUCTION_ITEM_LIST_UPDATE" then
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if findPrivate.isScanning then
findPrivate.timeDelay = 0
TSMAPI:CancelFrame("auctionFindScanDelay")
-- now that our query was successful we can get our data
findPrivate:ScanAuctions()
end
end
end
findPrivate.findFrame:SetScript("OnEvent", eventHandler)
findPrivate.findFrame:RegisterEvent("AUCTION_HOUSE_CLOSED")
findPrivate.findFrame:RegisterEvent("AUCTION_HOUSE_SHOW")
local function CompareTableKeys(tbl1, tbl2)
for _, key in ipairs(findPrivate.keys) do
if tbl1[key] ~= tbl2[key] then
return
end
end
return true
end
local function IsTargetAuction(index)
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", index))
-- local _, _, count, _, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, _, seller, seller_full = GetAuctionItemInfo("list", index)
local _, _, count, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, _, seller = GetAuctionItemInfo("list", index)
seller = TSM:GetAuctionPlayer(seller, nil)
local bid = bidAmount == 0 and minBid or bidAmount
local tmp = { itemString = itemString, count = count, bid = bid, buyout = buyout, seller = seller }
return CompareTableKeys(tmp, findPrivate.targetInfo)
end
-- valid targetInfo keys: itemString, count, bid, buyout, seller
function TSMAPI.AuctionScan:FindAuction(callback, targetInfo, useCache)
if findPrivate.isScanning then TSMAPI.AuctionScan:StopFindScan() end
findPrivate.keys = { "itemString", "count", "bid", "buyout", "seller" }
for i = #findPrivate.keys, 1, -1 do
if not targetInfo[findPrivate.keys[i]] then
tremove(findPrivate.keys, i)
end
end
local cacheIndex
if useCache and scanCache[targetInfo.itemString] then
for i, v in ipairs(scanCache[targetInfo.itemString]) do
if CompareTableKeys(v, targetInfo) then
cacheIndex = i
break
end
end
end
if cacheIndex then
findPrivate.page = floor((cacheIndex - 1) / 50)
findPrivate.query = scanCache[targetInfo.itemString].query
else
local name, _, rarity, _, minLevel, class, subClass = TSMAPI:GetSafeItemInfo(targetInfo.itemString)
findPrivate.query = { name = name, minLevel = minLevel, maxLevel = minLevel, class = class, subClass = subClass, rarity = rarity }
findPrivate.page = 0
end
findPrivate.targetInfo = targetInfo
findPrivate.callback = callback
findPrivate.cacheIndex = cacheIndex
findPrivate.isScanning = targetInfo.itemString
findPrivate.retries = 0
findPrivate.hardRetry = nil
-- check if the item is on the current page
for i = 1, GetNumAuctionItems("list") do
if IsTargetAuction(i) then
TSMAPI.AuctionScan:StopFindScan()
TSMAPI:CreateTimeDelay("queryFoundDelay", 0.1, function() findPrivate.callback(i) end)
return
end
end
findPrivate:SendQuery()
end
-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay)
function findPrivate:SendQuery()
if not findPrivate.isScanning then return end
if CanSendAuctionQuery() then
-- stop delay timer
TSMAPI:CancelFrame("auctionFindQueryDelay")
-- query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire)
findPrivate.findFrame:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
local q = findPrivate.query
QueryAuctionItems(q.name, q.minLevel, q.maxLevel, q.invType, q.class, q.subClass, findPrivate.page, 0, q.rarity)
else
-- run delay timer then try again to scan
TSMAPI:CreateTimeDelay("auctionFindQueryDelay", 0.05, function() findPrivate:SendQuery() end)
end
end
-- scans the currently shown page of auctions and collects all the data
function findPrivate:ScanAuctions()
if not findPrivate.isScanning then return end
-- collects data on the query:
-- # of auctions on current page
-- # of pages total
local shown, total = GetNumAuctionItems("list")
local totalPages = math.ceil(total / 50)
local dataIsBad, temp = private:ScanAuctionPage(findPrivate.targetInfo.seller)
-- Check for bad data
if findPrivate.retries < 3 then
if dataIsBad then
if findPrivate.hardRetry then
-- Hard retry
-- re-sends the entire query
findPrivate.retries = findPrivate.retries + 1
findPrivate:SendQuery()
else
-- Soft retry
-- runs a delay and then tries to scan the query again
findPrivate.timeDelay = findPrivate.timeDelay + BASE_DELAY
TSMAPI:CreateTimeDelay("auctionFindScanDelay", BASE_DELAY, findPrivate.ScanAuctions)
-- If after 4 seconds of retrying we still don't have data, will go and requery to try and solve the issue
-- if we still don't have data, we try to scan it anyway and move on.
if findPrivate.timeDelay >= 4 then
findPrivate.hardRetry = true
findPrivate.retries = 0
end
end
return
end
end
findPrivate.hardRetry = nil
findPrivate.retries = 0
-- now that we know our query is good, time to verify and then store our data
for i = 1, shown do
if IsTargetAuction(temp[i].index) then
TSMAPI.AuctionScan:StopFindScan()
return findPrivate.callback(temp[i].index, findPrivate.cacheIndex == findPrivate.page and findPrivate.page * 50 + temp[i].index)
end
end
-- This query has more pages to scan
-- increment the page # and send the new query
if not findPrivate.cacheIndex and totalPages > (findPrivate.page + 1) then
findPrivate.page = findPrivate.page + 1
findPrivate:SendQuery()
return
end
-- we are done scanning!
TSMAPI.AuctionScan:StopFindScan()
return findPrivate.callback()
end
-- returns whether or not we're currently doing a find scan
function TSMAPI.AuctionScan:IsFindScanning()
return findPrivate.isScanning
end
-- stops the scan because it was either interrupted or it was completed successfully
function TSMAPI.AuctionScan:StopFindScan()
findPrivate.findFrame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
findPrivate.isScanning = nil
TSMAPI:CancelFrame("auctionFindQueryDelay")
TSMAPI:CancelFrame("auctionFindScanDelay")
end

116
TradeSkillMaster/Auction/AuctionUtil.lua

@ -0,0 +1,116 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionUtil_private")
LibStub("AceEvent-3.0"):Embed(private)
local eventFrame = CreateFrame("Frame")
eventFrame:Hide()
eventFrame.data = {}
eventFrame.callback = function() end
eventFrame:SetScript("OnEvent", function(self, event, ...)
if self.interrupt and event == self.interrupt.event and self.interrupt.callback() then
self:UnregisterAllEvents()
self.data = {}
end
for i=1, #self.data do
if self.data[i].event == event then
if self.data[i].callback then
if self.data[i].callback(event, ...) then
tremove(self.data, i)
self:UnregisterEvent(event)
end
else
tremove(self.data, i)
self:UnregisterEvent(event)
end
break
end
end
if #self.data == 0 then
self:Hide()
self.callback()
end
end)
local function WaitForEvents(data, callback, interrupt)
eventFrame.data = data
eventFrame.callback = callback
for i=1, #data do
eventFrame:RegisterEvent(data[i].event)
end
if interrupt then
eventFrame.interrupt = interrupt
eventFrame:RegisterEvent(interrupt.event)
end
eventFrame:Show()
end
function TSMAPI:CreateEventDelay(event, callback, timeout, validator)
if not event then return end
local eventName = "eventDelay"..random()
if timeout then
TSMAPI:CreateTimeDelay(eventName, timeout, function() eventFrame:Hide() end)
callback()
end
WaitForEvents({event=event, callback=validator}, function() callback() TSMAPI:CancelFrame(eventName) end)
end
-- Sends the "TSM_AH_EVENTS" message once the action (buyout/bid/cancel/post)
-- has been acknowledged by the server and the client has been notified
function TSMAPI:WaitForAuctionEvents(mode, isMultiPost)
local function ValidateEvent(_, msg)
if mode == "Buyout" then
return msg:match(gsub(ERR_AUCTION_BID_PLACED, "%%s", ""))
elseif mode == "Cancel" then
return msg == ERR_AUCTION_REMOVED
elseif mode == "Post" then
return msg == ERR_AUCTION_STARTED
end
end
local events, interrupt
if mode == "Buyout" then
events = {{event="AUCTION_ITEM_LIST_UPDATE"}, {event="CHAT_MSG_SYSTEM", callback=ValidateEvent}}
interrupt = {event="UI_ERROR_MESSAGE", callback=function(_,msg) return msg == ERR_AUCTION_HIGHER_BID end}
elseif mode == "Cancel" then
events = {{event="CHAT_MSG_SYSTEM", callback=ValidateEvent}, {event="AUCTION_OWNED_LIST_UPDATE"}}
elseif mode == "Post" then
if isMultiPost then
events = {{event="AUCTION_MULTISELL_UPDATE", callback=function(_,arg1,arg2) return arg1 == arg2 end}}
else
events = {{event="CHAT_MSG_SYSTEM", callback=ValidateEvent}}
end
end
if events then
WaitForEvents(events, function() private:SendMessage("TSM_AH_EVENTS", mode) end, interrupt)
end
end
function TSMAPI:GetAuctionPercentColor(percent)
local colors = {
{color="|cff2992ff", value=50}, -- blue
{color="|cff16ff16", value=80}, -- green
{color="|cffffff00", value=110}, -- yellow
{color="|cffff9218", value=135}, -- orange
{color="|cffff0000", value=math.huge}, -- red
}
for i=1, #colors do
if percent < colors[i].value then
return colors[i].color
end
end
return "|cffffffff"
end

442
TradeSkillMaster/ChangeLog.txt

@ -0,0 +1,442 @@
v2.8.3
*Made TSMAPI:IsSoulbound() MUCH more efficient which will GREATLY reduce the interface log of all modules..
v2.8.2
*Added LibChatAnims to fix Blizzard bug with adding chat filters.
v2.8.1
*Updated connected realms.
*Added some more validation to price sources to avoid lua errors.
v2.8
*Fixed issue with TSM error handler.
*Removed 1.x dealfinding / shopping list support for importing items to groups.
*Added options for displaying prospect and mill values in tooltips (enabled by default).
*Reorganized general tooltip options slightly.
*Fixed bug with "/tsm version" not going in the right chat tab.
*Added confirmation for copying a profile.
v2.7
*Added new "check" function for doing logic operations in custom prices.
*Fixed bug with multiple fixed gold values in custom prices being invalid in certain situations.
*Removed "Status / Credits" tab and put credits at the bottom of the first tab instead.
*Modified the "New Group Name" editbox to contain the current group name by default.
*Made external price sources (mainly from Auctioneer) more consistent with TSM price sources.
v2.6.2
*Added verify for a sync setup which has been corrupted due to manual copying of saved variables.
*Added help button to main TSM frame for opening TSM assistant.
*Made lines under icons in main TSM frame shorter so they don't interfere with the title text.
v2.6.1
*Fixed typo in Assistant step.
*Made Assistant window wider.
v2.6
*Added new TSM Assistant feature!
*Removed SetUnit hook from LibExtraTip to avoid errors.
*Updated localized strings.
*Updated hard-coded list of connected realms.
v2.5.14
*Added line to TSM Info / Help page.
v2.5.13
*Fixed issue with importing groups with spaces in the subgroup names.
*Fixed bug with post confirmation window and removed coloring of g/s/c letters.
v2.5.12
*Fixed bug introduced in v2.5.11 with percentages in price sources.
v2.5.11
*Fixed bug with parsing price sources with multiple percentages.
v2.5.10
*Added functionality to TSMAPI functions including auto-complete support for editboxes.
*Minor code cleanup.
v2.5.9
*Fixed bug which was causing lua errors in TSM_Shopping.
v2.5.8
*Minor bug fix.
v2.5.7
*Improved TSM:GROUPS:* events for external usage.
v2.5.6
*Added TUJ as optional dependency to ensure it loads first.
v2.5.5
*Fixed bug with importing operations.
v2.5.4
*Cleaned up some code in the error handler and made it more resilient.
v2.5.3
*No change - fixing issue with curse packager.
v2.5.2
*Fixed bug with new change in v2.5.1.
v2.5.1
*Fixed bug with preparing filters taking a very long time.
*Removed name from auction results row.
*Removed some debug code.
v2.5
*Added error message when you try and move a group to one of its subgroups.
*Reorganized and cleaned up the code.
*Added option for embedding TSM's tooltip lines (enabled by default to maintain prior behavior).
*TSM's error handler will now ignore errors from auc-stat-wowuction.
*Fixed issue with multi-account syncing in patch 5.4.7.
v2.4.5
*Fixed issue with resizing the TSM window.
*Added some debug code.
*Potentially fixed bug with scans scanning too many pages due to missing item info.
*Fixed bug with main window not correctly saving its position and size between sessions.
*Fixed bug with moving the main window from the TSM icon
*Added TSMAPI:Assert for unexpected conditions which should be reported as errors to the TSM team.
*Added TSMAPI:Verify for conditions which require corrective action by the user and are not addon bugs.
*Soulbound items will no longer be moved for warehousing/auctioning options on the BankUI.
*Added events and event logger. Improved error handler.
*Fixed bug with tooltips having a delayed update after modifier keys are pressed.
*Errors occurring within threads should now be properly reported.
*Added vanilla gems to conversions.
*Added TSMAPI:GetConnectedRealms() to allow for basic connected realm support.
*Fixed bug with multi-account code constantly trying to add people to the friends list.
v2.4.4
*Fixed bug with TSM tooltip.
*Cleaned up some code.
v2.4.3
*Added tooltip options for displaying custom price sources in tooltips.
v2.4.2
*Fixed stack overflow issue with importing of large groups.
v2.4.1
*No change, trying to fix issue with curse.
v2.4
*Implemented new method for modules to export data to the TSM app.
*Changed red group color to be orange instead.
*Added support for scanning the last page (used by the Sniper feature of TSM_Shopping).
*Cleaned up much of the Auction scanning code.
*Made the "max" labels in the post frame clickable buttons.
*Added support for tabbing between fields in the post frame.
*Fixed some issues with prices changing incorrectly in the post frame when certain fields were modified.
*Added caching of battlepet names on-load to reduce errors from incomplete battlepet info.
v2.3.2
*Fixed some display issues with slider tooltips.
v2.3.1
*Fixed bug with common search term generation for items which have overlapping names (ie gems).
*Fixed bug in group import code.
v2.3
*TSM will now take into account common classes when generating AH query.
*Added option (enabled by default) to color group names within group trees based on their sub-group depth.
*Added display of locals to error handler.
*Fixed bug in common search term code.
v2.2.10
*Added [Jard's Peculiar Energy Source] to soulbound mats.
v2.2.9
*Fixed bug with adding a special character when renaming a group.
v2.2.8
*Fixed bug with battle pets.
v2.2.7
*Groups in grouptrees will now be selected by default.
*Greatly improved TSM's display of error messages.
*Fixed bug in TSMAPI:CreateTimeDelay which caused label-less timers to collide.
v2.2.6
*Fixed an issue with common search terms for items whose name matches the common search term (ie uncut gems).
v2.2.5
*Fixed bug with the current profile not being saved.
*Fixed bug with getting battle pet item info.
*Fixed sorting of auction result table.
*Removing an operation from a group will no longer switch to the new operation page.
v2.2.4
*Made group trees select all groups by default.
v2.2.3
*Fixed bug with formatted gold amounts in custom prices.
v2.2.2
*Fixed issue with using itemlinks in custom prices.
v2.2.1
*Fixed issue with group trees not remember their selection status in some situations.
v2.2
*Fixed bug with getting battle pet item info.
*Removed TSMAPI function to disable TSM error handler from packaged versions.
*Added list of groups which an operation is currently applied to, along with a remove button for each group, to the management tab of operations.
*Typing "/tsm freset" will now reset the position of all movable frames from all modules.
*Group trees will now remember which groups are selected (all will be deselected by default).
*Custom prices will now support any number of formatted gold values (instead of just one).
*Renamed TSMSelectionList to TSMGroupItemList interally.
*Added "avg()" function support to custom prices.
v2.1.14
*Fixed typo in code which caused issues with the bank UI and the DufUIBank addon.
*Fixed issues with sliders and treegroups caused by 5.4 changes.
v2.1.13
*Grabbing latest version of AccurateTime (again).
v2.1.12
*Grabbing latest version of AccurateTime.
v2.1.11
*Added some missing soulbound mats to internal lookup table.
*AccurateTime will now be embedded instead of standalone.
v2.1.10
*Fixed issue with '/tsm bankui' creating duplicate windows.
*Fixed bug with auction result sorting.
*Fixed some memory leaks.
v2.1.9
*Added new !AccurateTime library and fixed some issues around debugprofilestart/stop usage.
*Cleaned up .toc file a bit.
*Added bankui support for DufUIBank addon.
v2.1.8
*The BankUI will now remember its position (independantly for bank and guild bank).
*Added /tsm bankuireset to reset the BankUI frame position
v2.1.7
*Added caching to various commonly used APIs.
*Bug fix with item info caching.
*Added disenchant mats to tooltip.
v2.1.6
*added bankUI support for cargBags Nivaya
v2.1.5
*Fixed bug with clicking on scrolling table columns.
v2.1.4
*Added latent kor'kron pieces as non-disenchantable.
*Fixed bug with tree groups.
v2.1.3
*Fixed some conversion/destroying ratios.
*Fixed some patch 5.4 issues.
*Fixed a bug with switching profiles.
*Limited quantity items from a vendor will no longer be tracked.
*Updated TOC for patch 5.4.
v2.1.2
*Fixed various issues with disenchanting ratios.
*Fixed issue with soulbound materials.
*Made room for shopping tooltip options.
*Fixed bug with importing of subgroup structure.
v2.1.1
*Fixed bug with multiple occurrences of a custom price sources within a custom price.
*Fixed disenchanting ratios.
*Fixed tooltips for auction result rows.
*Improved how module icons are displayed in the main TSM window.
v2.1
*Some advanced features will now be designated as such with red title text.
*Added theme option for the color used to designate advanced features.
*Added the ability to import and export operations.
*Fixed issue with detecting disenchantable items on non-enUS clients.
*Added support for decimal places in percentages in custom prices.
*Added option to export/import subgroup structure when exporting/importing groups.
*Group selection trees will now remember their expanded/collapsed status info on a per-module basis.
*Shift-clicking the "<<< Remove" button in the "Items" tab of a group will now remove the items from all groups rather than move the items to their parent group.
*Filtering the item selection list in the "Items" tab for groups will now hide filtered-out rows.
*Added option for changing the chat tab which TSM and its modules use for printing messages.
*Added loop detection to custom price code.
*Fixed a bug with custom prices with specific items in them.
*Fixed error from missing localization phrase.
*Fixed stack overflow with very long custom prices.
*Added the ability to create custom price sources.
*Added more error checking to the custom price validation code.
v2.0.10
*Fix for error when alt-clicking buying in destroy mode.
v2.0.9
*Fixed some divide by zero issues found on 5.4 PTR.
*Fixed issue with bank updates going out to modules after bank was closed.
*Fixed bug with alt-click buying in destroy mode for TSM_Shopping.
v2.0.8
*Fixed bug with certain gold amounts not getting correctly formatted.
v2.0.7
*All scrolling tables will now have constant-height rows.
*Fixed bug with tooltip for battlepets.
v2.0.6
*Fixed issue with spaces in itemStrings.
v2.0.5
*Added option for including soulbound items in movement APIs.
*Fixed bug with syncing code trying to sync with the current character.
*Fixed various issues with manual posting.
v2.0.4
*Updated LibExtraTip.
*Fixed a bug with auction result tooltips not working for battlepets.
*TSM tooltip data will now be shown for battlepets as well as items.
v2.0.3
*Fixed a bug with vendorsell price source.
v2.0.2
*Fixed a bug with being unable to add ungrouped random enchant items as the base item.
*Fixed issue with first() and vendorsell in custom prices.
v2.0.1
*Fixed a bug with the version showing as "Dev" instead of v2.0.
v2.0
*First 2.0 Version!
\\
v1.6
*Updated TOC for patch 5.2.
*Added quick buyout feature to auction result frames.
*Added more theme options and preset themes.
*Adjusted milling/prospecting/disenchanting ratios as necessary.
*Added basic battle pet support.
*Improved the TSM error handler.
*Many other minor bug fixes and improvements.
v1.5
*Updated existing themes and added some new ones.
*Changed default theme to "Goblineer" as voted on by forum members.
*Updated TOC for patch 5.0.4
*TSM will now block all chat messages related to creating and canceling auctions.
*Added a slash command for resetting the position of the main TSM frame - '/tsm freset'.
*Fixed the green +/- buttons in treegroups.
*Made all the progress bars across the modules consistent.
*Rewrote all the auction results table code to address multiple issues.
*Added a slider to the TSM options tab for adjusting how many rows are shown in auction results tables.
*Many other minor bug fixes and improvements.
v1.4
*Redesigned the look and feel of the main TSM window and the auction house tab.
*Added a bunch of new options for customizing the new look.
*Added the ability to import / export appearance settings.
*Added a list of importable preset themes for TSM.
*There is now a hidden gem somewhere in TSM. Find it and something cool will happen :).
*Removed a few things which will cause errors in patch 5.0.4.
*Added support for the general stats which are now also included in the TUJ realm edition.
*TSM will now be packaged with a new version of LibAuctionScan which has significant scan speed improvements.
*Many other minor bug fixes and improvements.
v1.3
*Added further warnings for users who may still have TSM_Gathering installed.
*Fixed error caused by having Auctioneer enabled by not the Appraiser module.
*Performed a major reorganization of TSM's code to make it easier to find specific functions.
*Added tooltip support for selection lists.
*Fixed a bug with SelectionLists not adding/removing items that were selected by not visible.
*Updated all the disenchanting tables to fix a handful of inaccuracies.
*Fixed an error caused by changing the Auction House scale before opening it for the first time.
*SelectionList filters will now be parsed to avoid string pattern errors.
*Many other minor bug fixes and improvements.
v1.2
*Added destroying data for essences / shards / crystals for the Destroying feature in the Shopping module.
*Fixed various bugs with the TSM auction house tab / detached frame not displaying correctly.
*Added option for opening all your bags when the AH is shown.
*Added option for detaching the TSM auction house tab by default.
*The TSM auction house tab will now remember its detached possition throughout a single session.
*Removed localized strings from error handler and added client locale info.
*Many other minor bug fixes and improvements.
v1.1
*Improved TSM's error catcher.
*TSM's AH tab is now it's own window which can be detached from the main AH frame.
*When TSM's tab is attached, it'll hide behind the main AH frame when you switch tabs, allowing your TSM scan to continue "in the background".
*Added options for making the main AH frame movable and for changing the scale of the AH frame.
*Added an option for removing the bids from auction results to make the buyouts easier to read (bids are now hidden by default).
*Updated the LDBIcon to use a different texture than the minimap icon.
*Many other minor bug fixes and improvements.
v1.0
*First Release Version!
\\
**Beta Versions:**
v0.2.4
*Updated some APIs for a new feature that's in the works.
*Updated the TOC for patch 4.2.
v0.2.3
*Added support for spell tooltips.
*Removed the extra "v" that would show up infront of version numbers.
*Took out the Destroying button code (was moved to the Destroying module's code).
*Added some new info messages that'll appear in popups when the user logs in.
*Many other minor changes.
v0.2.2
*Added AceHook and lib-st libraries.
*Fixed a typo in one of the tips.
*Many other minor changes.
v0.2.1
*Finally fixed the bug where selectionlists (such as the one used for adding or removing items to Auctioning groups / categories) were only displaying the first 4 items under some circumstances.
*Updated the TOC for patch 4.1
v0.2
*Added TSM tips to the status bar of the main TSM window. The tip will change everytime the TSM window is open.
*Added a ton of support code for the AuctionDB and Destroying modules.
*Cleaned up the unused slash command code as well as a bunch of other parts of the code.
*Updated the credits.
*Many other changes that aren't evident to the user but better the addon's code as a whole.
v0.1.7
*Removed the slash command line from the minimap button tooltip as it was causing some errors.
*Tooltip functions can now access stack size info.
*Cleaned up the TSMMacroButton code and fixed some bugs.
v0.1.6
*SelectionLists should now clear the selected row on release.
*Added VersionKey APIs.
*Removed a few extra libraries from the TOC file / addon folder.
*Turned on no-lib creation.
*Updated the credits.
*Cleaned up some of the code.
v0.1.5
*Fixed a bug with the main TSM window on non-english clients.
*Minor improvements to the GUI functions.
*Added an option to the "Status" page for hiding the minimap icon.
v0.1.4
*Fixed some minor bugs with two of the TSMAPI functions.
*This update is required for using the latest version of Crafting
v0.1.3
*Adjusted the sidebar so it should resize more in order to not be blocked by right action bars.
*Fixed a bug with the selectionlists in auctioning sometimes only showing 4 items.
*Updated the included TSM Guidebook pdf.
v0.1.2
*The sidebar frame should no longer go off the screen.
*Added another popup to help new users.
v0.1.1
*Should be way more obvious when no modules are installed.
v0.1
*First Beta Release!

415
TradeSkillMaster/Core/ErrorHandler.lua

@ -0,0 +1,415 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- TSM's error handler.
local TSM = select(2, ...)
local AceGUI = LibStub("AceGUI-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
local origErrorHandler, ignoreErrors, isErrorFrameVisible, isAssert
TSMERRORLOG = {}
local tsmStack = {}
local stackNameLookup = {}
local addonSuites = {
{name="ArkInventory"},
{name="AtlasLoot"},
{name="Altoholic"},
{name="Auc-Advanced", commonTerm="Auc-"},
{name="Bagnon"},
{name="BigWigs"},
{name="Broker"},
{name="ButtonFacade"},
{name="Carbonite"},
{name="DataStore"},
{name="DBM"},
{name="Dominos"},
{name="DXE"},
{name="EveryQuest"},
{name="Forte"},
{name="FuBar"},
{name="GatherMate2"},
{name="Grid"},
{name="LightHeaded"},
{name="LittleWigs"},
{name="Masque"},
{name="MogIt"},
{name="Odyssey"},
{name="Overachiever"},
{name="PitBull4"},
{name="Prat-3.0"},
{name="RaidAchievement"},
{name="Skada"},
{name="SpellFlash"},
{name="TidyPlates"},
{name="TipTac"},
{name="Titan"},
{name="UnderHood"},
{name="WowPro"},
{name="ZOMGBuffs"},
}
local function StrStartCmp(str, startStr)
local startLen = strlen(startStr)
if startLen <= strlen(str) then
return strsub(str, 1, startLen) == startStr
end
end
local function GetModule(msg)
if strfind(msg, "TradeSkillMaster_") then
return strmatch(msg, "TradeSkillMaster_[A-Za-z]+")
elseif strfind(msg, "TradeSkillMaster\\") then
return "TradeSkillMaster"
end
return "?"
end
local function ExtractErrorMessage(...)
local msg = ""
for _, var in ipairs({...}) do
local varStr
local varType = type(var)
if varType == "boolean" then
varStr = var and "true" or "false"
elseif varType == "table" then
varStr = "<table>"
elseif varType == "function" then
varStr = "<function>"
elseif var == nil then
varStr = "<nil>"
else
varStr = var
end
msg = msg.." "..varStr
end
return msg
end
local function GetDebugStack()
local stackInfo = {}
local stackString = ""
local stack = debugstack(2) or debugstack(1)
if type(stack) == "string" then
local lines = {("\n"):split(stack)}
for _, line in ipairs(lines) do
local strStart = strfind(line, "in function")
if strStart and not strfind(line, "ErrorHandler.lua") then
line = gsub(line, "`", "<", 1)
line = gsub(line, "'", ">", 1)
local inFunction = strmatch(line, "<[^>]*>", strStart)
if inFunction then
inFunction = gsub(gsub(inFunction, ".*\\", ""), "<", "")
if inFunction ~= "" then
local str = strsub(line, 1, strStart-2)
str = strsub(str, strfind(str, "TradeSkillMaster") or 1)
if strfind(inFunction, "`") then
inFunction = strsub(inFunction, 2, -2)..">"
end
str = gsub(str, "TradeSkillMaster", "TSM")
tinsert(stackInfo, str.." <"..inFunction)
end
end
end
end
end
return table.concat(stackInfo, "\n")
end
local function GetTSMStack()
local stackInfo = {}
local index = #tsmStack
for i=1, 10 do -- only show up to 10 lines
if not tsmStack[index] then break end
tinsert(stackInfo, tsmStack[index])
index = index - 1
end
return table.concat(stackInfo, "\n")
end
local function GetEventLog()
local eventInfo = {}
local eventLog = TSM:GetEventLog()
for i, entry in ipairs(eventLog) do
tinsert(eventInfo, format("%d | %s | %s", i, entry.event, tostring(entry.arg)))
end
return table.concat(eventInfo, "\n")
end
local function GetAddonList()
local hasAddonSuite = {}
local addons = {}
local addonString = ""
for i = 1, GetNumAddOns() do
local name, _, _, enabled = GetAddOnInfo(i)
local version = GetAddOnMetadata(name, "X-Curse-Packaged-Version") or GetAddOnMetadata(name, "Version") or ""
if enabled then
local isSuite
for _, addonSuite in ipairs(addonSuites) do
local commonTerm = addonSuite.commonTerm or addonSuite.name
if StrStartCmp(name, commonTerm) then
isSuite = commonTerm
break
end
end
if isSuite then
if not hasAddonSuite[isSuite] then
tinsert(addons, {name=name, version=version})
hasAddonSuite[isSuite] = true
end
elseif StrStartCmp(name, "TradeSkillMaster") then
tinsert(addons, {name=gsub(name, "TradeSkillMaster", "TSM"), version=version})
else
tinsert(addons, {name=name, version=version})
end
end
end
for i, addonInfo in ipairs(addons) do
local info = addonInfo.name .. " (" .. addonInfo.version .. ")"
if i == #addons then
addonString = addonString .. " " .. info
else
addonString = addonString .. " " .. info .. "\n"
end
end
return addonString
end
local function ShowError(msg, isVerify)
if not AceGUI then
TSMAPI:CreateTimeDelay("errHandlerShowDelay", 0.1, function()
if AceGUI and UIParent then
CancelFrame("errHandlerShowDelay")
ShowError(msg, isVerify)
end
end, 0.1)
return
end
local f = AceGUI:Create("TSMWindow")
f:SetCallback("OnClose", function(self) isErrorFrameVisible = false AceGUI:Release(self) end)
f:SetTitle(L["TradeSkillMaster Error Window"])
f:SetLayout("Flow")
f:SetWidth(500)
f:SetHeight(400)
local l = AceGUI:Create("Label")
l:SetFullWidth(true)
l:SetFontObject(GameFontNormal)
if isVerify then
l:SetText(L["Looks like TradeSkillMaster has detected an error with your configuration. Please address this in order to ensure TSM remains functional."].."\n"..L["|cffffff00DO NOT report this as an error to the developers.|r If you require assistance with this, make a post on the TSM forums instead."].."|r")
else
l:SetText(L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by copying the entire error below and following the instructions for reporting bugs listed here (unless told elsewhere by the author):"].." |cffffff00http://tradeskillmaster.com/wiki|r")
end
f:AddChild(l)
local heading = AceGUI:Create("Heading")
heading:SetText("")
heading:SetFullWidth(true)
f:AddChild(heading)
local eb = AceGUI:Create("MultiLineEditBox")
eb:SetLabel(L["Error Info:"])
eb:SetMaxLetters(0)
eb:SetFullWidth(true)
eb:SetText(msg)
eb:DisableButton(true)
eb:SetFullHeight(true)
f:AddChild(eb)
f.frame:SetFrameStrata("FULLSCREEN_DIALOG")
f.frame:SetFrameLevel(100)
isErrorFrameVisible = true
end
function TSM:IsValidError(...)
if ignoreErrors then return end
ignoreErrors = true
local msg = ExtractErrorMessage(...)
ignoreErrors = false
if not strfind(msg, "TradeSkillMaster") then return end
if strfind(msg, "auc%-stat%-wowuction") then return end
return msg
end
function TSMAPI:Verify(cond, err)
if cond then return end
ignoreErrors = true
tinsert(TSMERRORLOG, err)
if not isErrorFrameVisible then
TSM:Print(L["Looks like TradeSkillMaster has detected an error with your configuration. Please address this in order to ensure TSM remains functional."])
ShowError(err, true)
elseif isErrorFrameVisible == true then
TSM:Print(L["Additional error suppressed"])
isErrorFrameVisible = 1
end
ignoreErrors = false
end
local function TSMErrorHandler(msg)
-- ignore errors while we are handling this error
ignoreErrors = true
TSMERRORTEMP = msg
local color = TSMAPI.Design and TSMAPI.Design:GetInlineColor("link2") or ""
local color2 = TSMAPI.Design and TSMAPI.Design:GetInlineColor("advanced") or ""
local errorMessage = ""
errorMessage = errorMessage..color.."Addon:|r "..color2..GetModule(msg).."|r\n"
errorMessage = errorMessage..color.."Message:|r "..msg.."\n"
errorMessage = errorMessage..color.."Date:|r "..date("%m/%d/%y %H:%M:%S").."\n"
errorMessage = errorMessage..color.."Client:|r "..GetBuildInfo().."\n"
errorMessage = errorMessage..color.."Locale:|r "..GetLocale().."\n"
errorMessage = errorMessage..color.."Stack:|r\n"..GetDebugStack().."\n"
errorMessage = errorMessage..color.."TSM Stack:|r\n"..GetTSMStack().."\n"
errorMessage = errorMessage..color.."Local Variables:|r\n"..(debuglocals(isAssert and 5 or 4) or "").."\n"
errorMessage = errorMessage..color.."TSM Event Log:|r\n"..GetEventLog().."\n"
errorMessage = errorMessage..color.."Addons:|r\n"..GetAddonList().."\n"
tinsert(TSMERRORLOG, errorMessage)
if not isErrorFrameVisible then
TSM:Print(L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by following the instructions shown."])
ShowError(errorMessage)
elseif isErrorFrameVisible == true then
TSM:Print(L["Additional error suppressed"])
isErrorFrameVisible = 1
end
-- need to clear the stack
tsmStack = {}
ignoreErrors = false
end
function TSMAPI:Assert(cond, err)
if cond then return end
isAssert = true
TSMErrorHandler(err)
isAssert = false
end
do
origErrorHandler = geterrorhandler()
local errHandlerFrame = CreateFrame("Frame", nil, nil, "TSMErrorHandlerTemplate")
errHandlerFrame.errorHandler = TSMErrorHandler
errHandlerFrame.origErrorHandler = origErrorHandler
seterrorhandler(errHandlerFrame.handler)
end
--[===[@debug@
--- Disables TSM's error handler until the game is reloaded.
-- This is mainly used for debugging errors with TSM's error handler and should not be used in actual code.
function TSMAPI:DisableErrorHandler()
seterrorhandler(origErrorHandler)
end
--@end-debug@]===]
-- other debug functions
TSMAPI.Debug = {}
local dumpDefaults = {
DEVTOOLS_MAX_ENTRY_CUTOFF = 30, -- Maximum table entries shown
DEVTOOLS_LONG_STRING_CUTOFF = 200, -- Maximum string size shown
DEVTOOLS_DEPTH_CUTOFF = 10, -- Maximum table depth
}
function TSMAPI.Debug:DumpTable(tbl, maxDepth, maxItems, maxStr)
DEVTOOLS_DEPTH_CUTOFF = maxDepth or dumpDefaults.DEVTOOLS_DEPTH_CUTOFF
DEVTOOLS_MAX_ENTRY_CUTOFF = maxItems or dumpDefaults.DEVTOOLS_MAX_ENTRY_CUTOFF
DEVTOOLS_DEPTH_CUTOFF = maxStr or dumpDefaults.DEVTOOLS_DEPTH_CUTOFF
if not IsAddOnLoaded("Blizzard_DebugTools") then
LoadAddOn("Blizzard_DebugTools")
end
DevTools_Dump(tbl)
for i, v in pairs(dumpDefaults) do
_G[i] = v
end
end
-- stack tracing functions
local function FormatTSMStack(obj, name, ...)
local args
for i=2, select('#', ...) do
local arg = select(i, ...)
local str
if stackNameLookup[arg] then
str = "<"..stackNameLookup[arg]..">"
elseif type(arg) == "table" then
if getmetatable(arg) and getmetatable(arg).__tostring then
str = "<"..tostring(arg)..">"
else
local _, addr = (":"):split(tostring(arg))
str = "table:"..tonumber(addr, 16)
end
elseif type(arg) == "string" then
str = '"'..tostring(arg)..'"'
elseif type(arg) == "function" then
local _, addr = (":"):split(tostring(arg))
str = "function:"..tonumber(addr, 16)
else
str = tostring(arg)
end
if args then
args = args..", "..str
else
args = str
end
end
local funcCall = "?"
if obj == select(1, ...) and args then
funcCall = (stackNameLookup[obj] or tostring(obj))..":"..name.."("..args..")"
end
return funcCall
end
-- this must be a separate function so we can return the ... after popping off the stack
local function TrackPopStack(...)
tremove(tsmStack, #tsmStack)
return ...
end
local function RegisterForTracing(obj, name)
stackNameLookup[obj] = name
for name, v in pairs(obj) do
if type(v) == "function" then
TSM:RawHook(obj, name, function(...)
tinsert(tsmStack, FormatTSMStack(obj, name, ...))
return TrackPopStack(v(...))
end)
end
end
end
function TSMAPI:RegisterForTracing(obj, name)
-- wait one frame to ensure all functions are declared
TSMAPI:CreateTimeDelay(0, function() RegisterForTracing(obj, name) end)
end

49
TradeSkillMaster/Core/EventLogger.lua

@ -0,0 +1,49 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- TSM's error handler.
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
local eventObj = TSMAPI:GetEventObject()
local currentIndex = 1
local NUM_LOG_ENTRIES = 20
local debugLog = {}
local alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_="
local base = #alpha
local alphaTable = {}
local alphaTableLookup = {}
for i = 1, base do
local char = strsub(alpha, i, i)
tinsert(alphaTable, char)
alphaTableLookup[char] = i
end
local function EventCallback(event, arg)
debugLog[currentIndex] = {event=event, arg=arg}
currentIndex = currentIndex + 1
if currentIndex > NUM_LOG_ENTRIES then
currentIndex = 1
end
end
eventObj:SetCallbackAnyEvent(EventCallback)
function TSM:GetEventLog()
local temp = {}
for i=1, #debugLog do
local index = currentIndex - i
if index <= 0 then
index = index + NUM_LOG_ENTRIES
end
tinsert(temp, debugLog[index])
end
return temp
end

62
TradeSkillMaster/Core/Events.lua

@ -0,0 +1,62 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- TSM's event handler.
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Events_private")
private.objects = {}
private.eventObjectCallbacks = {
SetCallbackAnyEvent = function(self, callback)
self._anyEventCallback = callback
end,
SetCallback = function(self, event, callback, matchAll)
self._callbacks[event] = {func = callback, matchAll = (matchAll and true or false)} -- need to convert matchAll to a boolean
end,
ClearAllCallbacks = function(self)
wipe(self._callbacks)
end
}
function TSMAPI:GetEventObject()
local obj = {}
obj._callbacks = {}
obj._anyEventCallback = nil
for name, func in pairs(private.eventObjectCallbacks) do
obj[name] = func
end
tinsert(private.objects, obj)
return obj
end
function private:OnEventFired(event, arg, fullEvent)
local isPartial = event ~= fullEvent and true or false
for _, obj in ipairs(private.objects) do
if not isPartial and obj._anyEventCallback then
obj._anyEventCallback(fullEvent, arg)
end
local callback = obj._callbacks[event]
if callback then
if isPartial == callback.matchAll then
callback.func(fullEvent, arg)
end
end
end
end
function TSMAPI:FireEvent(event, arg)
local parts = {(":"):split(event)}
for i=1, #parts do
local partialEvent = table.concat(parts, ":", 1, i)
private:OnEventFired(partialEvent, arg, event)
end
end

1687
TradeSkillMaster/Core/Groups.lua

File diff suppressed because it is too large Load Diff

368
TradeSkillMaster/Core/Modules.lua

@ -0,0 +1,368 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains all the code for the new standardized module registration / format
local TSM = select(2, ...)
local Modules = TSM:NewModule("Modules", "AceConsole-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local moduleObjects = TSM.moduleObjects
local moduleNames = TSM.moduleNames
-- initialization stuff
function Modules:OnEnable()
-- register the chat commands (slash commands) - whenver '/tsm' or '/tradeskillmaster' is typed by the user, Modules:ChatCommand() will be called
Modules:RegisterChatCommand("tsm", "ChatCommand")
Modules:RegisterChatCommand("tradeskillmaster", "ChatCommand")
-- tooltip setup
TSM:SetupTooltips()
-- no modules popup
TSMAPI:CreateTimeDelay("noModulesPopup", 3, function()
if #moduleNames == 1 then
StaticPopupDialogs["TSMInfoPopup"] = {
text = L["|cffffff00Important Note:|r You do not currently have any modules installed / enabled for TradeSkillMaster! |cff77ccffYou must download modules for TradeSkillMaster to have some useful functionality!|r\n\nPlease visit http://www.curse.com/addons/wow/tradeskill-master and check the project description for links to download modules."],
button1 = L["I'll Go There Now!"],
timeout = 0,
whileDead = true,
OnAccept = function() TSM:Print(L["Just incase you didn't read this the first time:"]) TSM:Print(L["|cffffff00Important Note:|r You do not currently have any modules installed / enabled for TradeSkillMaster! |cff77ccffYou must download modules for TradeSkillMaster to have some useful functionality!|r\n\nPlease visit http://www.curse.com/addons/wow/tradeskill-master and check the project description for links to download modules."]) end,
preferredIndex = 3,
}
TSMAPI:ShowStaticPopupDialog("TSMInfoPopup")
end
end)
end
-- **************************************************************************
-- TSMAPI:NewModule API
-- **************************************************************************
-- info on all the possible fields of the module objects which TSM core cares about
local moduleFieldInfo = {
-- operation fields
{ key = "operations", type = "table", subFieldInfo = { maxOperations = "number", callbackOptions = "function", callbackInfo = "function" } },
-- tooltip fields
{ key = "GetTooltip", type = "function" },
-- tooltip options
{ key = "tooltipOptions", type = "table", subFieldInfo = { callback = "function" } },
-- shared feature fields
{ key = "slashCommands", type = "table", subTableInfo = { key = "string", label = "string", callback = "function" } },
{ key = "icons", type = "table", subTableInfo = { side = "string", desc = "string", callback = "function", icon = "string" } },
{ key = "auctionTab", type = "table", subFieldInfo = { callbackShow = "function", callbackHide = "function" } },
{ key = "bankUiButton", type = "table", subFieldInfo = { callback = "function" } },
-- data access fields
{ key = "priceSources", type = "table", subTableInfo = { key = "string", label = "string", callback = "function" } },
{ key = "moduleAPIs", type = "table", subTableInfo = { key = "string", callback = "function" } },
-- multi-account sync fields
{ key = "sync", type = "table", subFieldInfo = { callback = "function" } },
}
-- if the passed function is a string, will check if it's a method of the object and return a wrapper function
function Modules:GetFunction(obj, func)
if type(func) == "string" then
local part1, part2 = (":"):split(func)
if part2 and obj[part1] and obj[part1][part2] then
return function(...) return obj[part1][part2](obj[part1], ...) end
elseif obj[part1] then
return function(...) return obj[part1](obj, ...) end
end
end
return func
end
-- validates a simple list of sub-tables which have the basic key/label/callback fields
function Modules:ValidateList(obj, val, keys)
for i, v in ipairs(val) do
if type(v) ~= "table" then
return "invalid entry in list at index " .. i
end
for key, valType in pairs(keys) do
if valType == "function" then
v[key] = Modules:GetFunction(obj, v[key])
end
if type(v[key]) ~= valType then
return format("expected %s type for field %s, got %s at index %d", valType, key, type(v[key]), i)
end
end
end
end
function Modules:ValidateModuleObject(obj)
-- make sure it's a table
if type(obj) ~= "table" then
return format("Expected table, got %s.", type(obj))
end
-- simple check that it's an AceAddon object which stores the name in .name and implements a .__tostring metamethod.
if tostring(obj) ~= obj.name then
return "Passed object is not an AceAddon-3.0 object."
end
-- validate all the fields
for _, fieldInfo in ipairs(moduleFieldInfo) do
local val = obj[fieldInfo.key]
if val then
-- make sure it's of the correct type
if type(val) ~= fieldInfo.type then
return format("For field '%s', expected type of %s, got %s.", fieldInfo.key, fieldInfo.type, type(val))
end
-- if there's required subfields, check them
if fieldInfo.subFieldInfo then
for key, valType in pairs(fieldInfo.subFieldInfo) do
if valType == "function" then
val[key] = Modules:GetFunction(obj, val[key])
end
if type(val[key]) ~= valType then
return format("expected %s type for field %s, got %s at index %d", valType, key, type(val[key]), key)
end
end
end
-- if there's subTableInfo specified, run Modules:ValidateList on this field
if fieldInfo.subTableInfo then
local errMsg = Modules:ValidateList(obj, val, fieldInfo.subTableInfo)
if errMsg then
return format("Invalid value for '%s': %s.", fieldInfo.key, errMsg)
end
end
end
end
end
function Modules:GetInfo()
local info = {}
for _, name in ipairs(moduleNames) do
local obj = moduleObjects[name]
tinsert(info, { name = name, version = obj._version, author = obj._author, desc = obj._desc })
end
return info
end
function TSMAPI:NewModule(obj)
local errMsg
if obj == TSM then
local tmp = TSM.operations
TSM.operations = nil
errMsg = Modules:ValidateModuleObject(obj)
TSM.operations = tmp
else
errMsg = Modules:ValidateModuleObject(obj)
end
if errMsg then
error(errMsg, 2)
end
-- register the db callback
if obj.db and obj.OnTSMDBShutdown then
obj.db:RegisterCallback("OnDatabaseShutdown", TSM.ModuleOnDatabaseShutdown)
end
-- register it for debug tracing
TSMAPI:RegisterForTracing(obj)
for _, subModule in pairs(obj.modules or {}) do
local name = obj.name.."."..subModule.moduleName
TSMAPI:RegisterForTracing(subModule, name)
end
-- sets the _version, _author, and _desc fields
local fullName = gsub(obj.name, "TSM_", "TradeSkillMaster_")
obj._version = GetAddOnMetadata(fullName, "X-Curse-Packaged-Version") or GetAddOnMetadata(fullName, "Version")
if strsub(obj._version, 1, 1) == "@" then
obj._version = "Dev"
end
obj._author = GetAddOnMetadata(fullName, "Author")
obj._desc = GetAddOnMetadata(fullName, "Notes")
-- store the object in the local table
local moduleName = gsub(obj.name, "TradeSkillMaster_", "")
moduleName = gsub(obj.name, "TSM_", "")
moduleObjects[moduleName] = obj
tinsert(moduleNames, moduleName)
sort(moduleNames, function(a, b)
if a == "TradeSkillMaster" then
return true
elseif b == "TradeSkillMaster" then
return false
else
return a < b
end
end)
-- register icons with main frame code
if obj.icons then
for _, info in ipairs(obj.icons) do
if info.slashCommand then
obj.slashCommands = obj.slashCommands or {}
tinsert(obj.slashCommands, {key=info.slashCommand, label=format("Opens the TSM window to the '%s' page", info.desc), callback=function() TSMAPI:OpenFrame() TSMAPI:SelectIcon(obj.name, info.desc) end})
end
TSM:RegisterMainFrameIcon(info.desc, info.icon, info.callback, obj.name, info.side)
end
end
-- register auction buttons with auction frame code
if obj.auctionTab then
TSM:RegisterAuctionFunction(moduleName, obj.auctionTab.callbackShow, obj.auctionTab.callbackHide)
end
if obj ~= TSM and obj.operations then
-- conversion code from early beta versions
if obj.db and obj.db.global.operations then
TSM.operations[moduleName] = CopyTable(obj.db.global.operations)
obj.db.global.operations = nil
end
TSM:RegisterOperationInfo(moduleName, obj.operations)
TSM.operations[moduleName] = TSM.operations[moduleName] or {}
obj.operations = TSM.operations[moduleName]
for _, operation in pairs(obj.operations) do
operation.ignorePlayer = operation.ignorePlayer or {}
operation.ignoreFactionrealm = operation.ignoreFactionrealm or {}
operation.relationships = operation.relationships or {}
end
TSM:CheckOperationRelationships(moduleName)
end
-- register tooltip options
if obj.tooltipOptions then
TSM:RegisterTooltipInfo(moduleName, obj.tooltipOptions)
end
-- register bankUi Tabs
if obj.bankUiButton then
TSM:RegisterBankUiButton(moduleName, obj.bankUiButton.callback)
end
-- -- register sync callback
-- if obj.sync then
-- TSM:RegisterSyncCallback(moduleName, obj.sync.callback)
-- end
-- replace default Print and Printf functions
local Print = obj.Print
obj.Print = function(self, ...) Print(self, TSMAPI:GetChatFrame(), ...) end
local Printf = obj.Printf
obj.Printf = function(self, ...) Printf(self, TSMAPI:GetChatFrame(), ...) end
end
function TSM:UpdateModuleProfiles()
if TSM.db.global.globalOperations then
for moduleName, obj in pairs(moduleObjects) do
if obj.operations then
TSM.db.global.operations[moduleName] = TSM.db.global.operations[moduleName] or {}
obj.operations = TSM.db.global.operations[moduleName]
end
end
TSM.operations = TSM.db.global.operations
else
for moduleName, obj in pairs(moduleObjects) do
if obj.operations then
TSM.db.profile.operations[moduleName] = TSM.db.profile.operations[moduleName] or {}
obj.operations = TSM.db.profile.operations[moduleName]
end
end
TSM.operations = TSM.db.profile.operations
end
for module, operations in pairs(TSM.operations) do
for _, operation in pairs(operations) do
operation.ignorePlayer = operation.ignorePlayer or {}
operation.ignoreFactionrealm = operation.ignoreFactionrealm or {}
operation.relationships = operation.relationships or {}
end
TSM:CheckOperationRelationships(module)
end
if not TSM.db.profile.design then
TSM:LoadDefaultDesign()
end
end
local didDBShutdown = false
function TSM:ModuleOnDatabaseShutdown()
if didDBShutdown then return end
didDBShutdown = true
local originalProfile = TSM.db:GetCurrentProfile()
for _, obj in pairs(moduleObjects) do
-- erroring here would cause the profile to be reset, so use pcall
if obj.OnTSMDBShutdown and not pcall(obj.OnTSMDBShutdown) then
-- the callback hit an error, so ensure the correct profile is restored
TSM.db:SetProfile(originalProfile)
end
end
-- ensure we're back on the correct profile
TSM.db:SetProfile(originalProfile)
end
function TSM:IsOperationIgnored(module, operationName)
local obj = moduleObjects[module]
local operation = obj.operations[operationName]
if not operation then return end
local factionrealm = TSM.db.keys.factionrealm
local playerKey = UnitName("player").." - "..factionrealm
return operation.ignorePlayer[playerKey] or operation.ignoreFactionrealm[factionrealm]
end
function TSM:CheckOperationRelationships(moduleName)
for _, operation in pairs(TSM.operations[moduleName]) do
for key, target in pairs(operation.relationships or {}) do
if not TSM.operations[moduleName][target] then
operation.relationships[key] = nil
end
end
end
end
-- **************************************************************************
-- Module APIs
-- **************************************************************************
function TSMAPI:ModuleAPI(moduleName, key, ...)
if type(moduleName) ~= "string" or type(key) ~= "string" then return nil, "Invalid args" end
if not moduleObjects[moduleName] then return nil, "Invalid module" end
for _, info in ipairs(moduleObjects[moduleName].moduleAPIs or {}) do
if info.key == key then
return info.callback(...)
end
end
return nil, "Key not found"
end
-- **************************************************************************
-- Slash Commands
-- **************************************************************************
function Modules:ChatCommand(input)
local parts = { (" "):split(input) }
local cmd, args = strlower(parts[1] or ""), table.concat(parts, " ", 2)
if cmd == "" then
TSMAPI:OpenFrame()
TSMAPI:SelectIcon("TradeSkillMaster", L["TSM Status / Options"])
else
local foundCmd
for _, obj in pairs(moduleObjects) do
if obj.slashCommands then
for _, info in ipairs(obj.slashCommands) do
if strlower(info.key) == cmd then
info.callback(args)
foundCmd = true
end
end
end
end
-- If not a registered command, print out slash command help
if not foundCmd then
local chatFrame = TSMAPI:GetChatFrame()
TSM:Print(L["Slash Commands:"])
chatFrame:AddMessage("|cffffaa00" .. L["/tsm|r - opens the main TSM window."])
chatFrame:AddMessage("|cffffaa00" .. L["/tsm help|r - Shows this help listing"])
for _, name in ipairs(moduleNames) do
for _, info in ipairs(moduleObjects[name].slashCommands or {}) do
chatFrame:AddMessage("|cffffaa00/tsm " .. info.key .. "|r - " .. info.label)
end
end
end
end
end

699
TradeSkillMaster/Core/Mover.lua

@ -0,0 +1,699 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains all the code for moving items between bags / bank.
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local lib = TSMAPI
local bankType
local fullMoves, splitMoves, bagState = {}, {}, {}
local callbackMsg = {}
-- this is a set of wrapper functions so that I can switch
-- between guildbank and bank function easily (taken from warehousing)
TSM.pickupContainerItemSrc = nil
TSM.getContainerItemIDSrc = nil
TSM.getContainerNumSlotsSrc = nil
TSM.getContainerItemLinkSrc = nil
TSM.getContainerNumFreeSlotsSrc = nil
TSM.splitContainerItemSrc = nil
TSM.pickupContainerItemDest = nil
TSM.getContainerItemIDDest = nil
TSM.getContainerNumSlotsDest = nil
TSM.getContainerItemLinkDest = nil
TSM.getContainerNumFreeSlotsDest = nil
TSM.autoStoreItem = nil
TSM.getContainerItemQty = nil
function TSM:OnEnable()
local next = next
TSM:RegisterEvent("GUILDBANKFRAME_OPENED", function(event)
bankType = "guildbank"
end)
TSM:RegisterEvent("BANKFRAME_OPENED", function(event)
bankType = "bank"
end)
TSM:RegisterEvent("GUILDBANKFRAME_CLOSED", function(event, addon)
bankType = nil
TSM:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED")
end)
TSM:RegisterEvent("BANKFRAME_CLOSED", function(event)
bankType = nil
TSM:UnregisterEvent("BAG_UPDATE")
end)
end
local function setSrcBagFunctions(bagType)
if bagType == "guildbank" then
TSM.autoStoreItem = function(bag, slot) AutoStoreGuildBankItem(bag, slot)
end
TSM.getContainerItemQty = function(bag, slot) return select(2, GetGuildBankItemInfo(bag, slot))
end
TSM.splitContainerItemSrc = function(bag, slot, need) SplitGuildBankItem(bag, slot, need);
end
TSM.pickupContainerItemSrc = function(bag, slot) PickupGuildBankItem(bag, slot)
end
TSM.getContainerNumSlotsSrc = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end
TSM.getContainerItemLinkSrc = function(bag, slot) return GetGuildBankItemLink(bag, slot)
end
TSM.getContainerNumFreeSlotsSrc = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end --need to change this eventually
TSM.getContainerItemIDSrc = function(bag, slot)
local tmpLink = GetGuildBankItemLink(bag, slot)
local quantity = select(2, GetGuildBankItemInfo(bag, slot))
if tmpLink then
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
else
return nil
end
end
else
TSM.autoStoreItem = function(bag, slot) UseContainerItem(bag, slot)
end
TSM.getContainerItemQty = function(bag, slot) return select(2, GetContainerItemInfo(bag, slot))
end
TSM.splitContainerItemSrc = function(bag, slot, need) SplitContainerItem(bag, slot, need)
end
TSM.pickupContainerItemSrc = function(bag, slot) PickupContainerItem(bag, slot)
end
TSM.getContainerItemIDSrc = function(bag, slot)
local tmpLink = GetContainerItemLink(bag, slot)
local quantity = select(2, GetContainerItemInfo(bag, slot))
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
end
TSM.getContainerNumSlotsSrc = function(bag) return GetContainerNumSlots(bag)
end
TSM.getContainerItemLinkSrc = function(bag, slot) return GetContainerItemLink(bag, slot)
end
TSM.getContainerNumFreeSlotsSrc = function(bag) return GetContainerNumFreeSlots(bag)
end
end
end
local function setDestBagFunctions(bagType)
if bagType == "guildbank" then
TSM.pickupContainerItemDest = function(bag, slot) PickupGuildBankItem(bag, slot)
end
TSM.getContainerNumSlotsDest = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end
TSM.getContainerNumFreeSlotsDest = function(bag) return GetEmptySlotCount(bag)
end --need to change this eventually
TSM.getContainerItemLinkDest = function(bag, slot) return GetGuildBankItemLink(bag, slot)
end
TSM.getContainerItemIDDest = function(bag, slot)
local tmpLink = GetGuildBankItemLink(bag, slot)
local quantity = select(2, GetGuildBankItemInfo(bag, slot))
if tmpLink then
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
else
return nil
end
end
else
TSM.pickupContainerItemDest = function(bag, slot) PickupContainerItem(bag, slot)
end
TSM.getContainerItemIDDest = function(bag, slot)
local tmpLink = GetContainerItemLink(bag, slot)
local quantity = select(2, GetContainerItemInfo(bag, slot))
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
end
TSM.getContainerNumSlotsDest = function(bag) return GetContainerNumSlots(bag)
end
TSM.getContainerItemLinkDest = function(bag, slot) return GetContainerItemLink(bag, slot)
end
TSM.getContainerNumFreeSlotsDest = function(bag) return GetContainerNumFreeSlots(bag)
end
end
end
local function getContainerTable(cnt)
local t = {}
if cnt == "bank" then
local numSlots, _ = GetNumBankSlots()
for i = 1, numSlots + 1 do
if i == 1 then
t[i] = -1
else
t[i] = i + 3
end
end
return t
elseif cnt == "guildbank" then
for i = 1, GetNumGuildBankTabs() do
local canView, canDeposit, stacksPerDay = GetGuildBankTabInfo(i);
if canView and canDeposit and stacksPerDay then
t[i] = i
end
end
return t
elseif cnt == "bags" then
for i = 1, NUM_BAG_SLOTS + 1 do t[i] = i - 1
end
return t
end
end
local function GetEmptySlots(container)
local emptySlots = {}
for i, bag in ipairs(getContainerTable(container)) do
if TSM.getContainerNumSlotsDest(bag) > 0 then
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if not TSM.getContainerItemIDDest(bag, slot) then
if not emptySlots[bag] then emptySlots[bag] = {}
end
table.insert(emptySlots[bag], slot)
end
end
end
end
return emptySlots
end
local function GetEmptySlotCount(bag)
local count = 0
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if not TSM.getContainerItemLinkDest(bag, slot) then
count = count + 1
end
end
if count ~= 0 then
return count
else
return false
end
end
local function canGoInBag(itemString, destTable)
local itemFamily = GetItemFamily(itemString)
local default
for _, bag in pairs(destTable) do
local bagFamily = GetItemFamily(GetBagName(bag)) or 0
if itemFamily and bagFamily and bagFamily > 0 and bit.band(itemFamily, bagFamily) > 0 then
if GetEmptySlotCount(bag) then
return bag
end
elseif bagFamily == 0 then
if GetEmptySlotCount(bag) then
if not default then
default = bag
end
end
end
end
return default
end
local function findExistingStack(itemLink, dest, quantity, gbank)
for i, bag in ipairs(getContainerTable(dest)) do
if gbank then
if bag == GetCurrentGuildBankTab() then
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if TSM.getContainerItemIDDest(bag, slot) == TSMAPI:GetBaseItemString(itemLink, true) then
local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemLink))
local _, currentQuantity = TSM.getContainerItemIDDest(bag, slot)
if currentQuantity and (currentQuantity + quantity) <= maxStack then
return bag, slot
end
end
end
end
else
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if TSM.getContainerItemIDDest(bag, slot) == TSMAPI:GetBaseItemString(itemLink, true) then
local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemLink))
local _, currentQuantity = TSM.getContainerItemIDDest(bag, slot)
if currentQuantity and (currentQuantity + quantity) <= maxStack then
return bag, slot
end
end
end
end
end
end
local function getTotalItems(src)
local results = {}
if src == "bank" then
for _, _, itemString, quantity in TSMAPI:GetBankIterator(true, true) do
results[itemString] = (results[itemString] or 0) + quantity
end
return results
elseif src == "guildbank" then
for bag = 1, GetNumGuildBankTabs() do
for slot = 1, MAX_GUILDBANK_SLOTS_PER_TAB or 98 do
local link = GetGuildBankItemLink(bag, slot)
local itemString = TSMAPI:GetBaseItemString(link, true)
if itemString then
local quantity = select(2, GetGuildBankItemInfo(bag, slot))
results[itemString] = (results[itemString] or 0) + quantity
end
end
end
return results
elseif src == "bags" then
for _, _, itemString, quantity in TSMAPI:GetBagIterator(true, true) do
results[itemString] = (results[itemString] or 0) + quantity
end
return results
end
end
function TSM.generateMoves(includeSoulbound)
if not TSM:areBanksVisible() then
wipe(splitMoves)
wipe(fullMoves)
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - You must be at a bank or guildbank"])
end
wipe(callbackMsg)
return
end
local next = next
local bagsFull, bankFull = false, false
local bagMoves, bankMoves = {}, {}
wipe(splitMoves)
wipe(fullMoves)
local currentBagState = getTotalItems("bags")
for itemString, quantity in pairs(bagState) do
local currentQty = currentBagState[itemString] or 0
if quantity < currentQty then
bagMoves[itemString] = currentQty - quantity
elseif quantity > currentQty then
bankMoves[itemString] = quantity - currentQty
end
end
if next(bagMoves) ~= nil then -- generate moves from bags to bank
setSrcBagFunctions("bags")
setDestBagFunctions(bankType)
for item, _ in pairs(bagMoves) do
for i, bag in ipairs(getContainerTable("bags")) do
for slot = 1, TSM.getContainerNumSlotsSrc(bag) do
local itemLink = TSM.getContainerItemLinkSrc(bag, slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
if itemString and itemString == item then
if not TSMAPI:IsSoulbound(bag, slot) or includeSoulbound then
local have = TSM.getContainerItemQty(bag, slot)
local need = bagMoves[itemString]
if have and need then
-- check if the source item stack can fit into a destination bag
local destBag
if bankType == "guildbank" then
destBag = findExistingStack(itemLink, bankType, min(have, need), true)
if not destBag then
if GetEmptySlotCount(GetCurrentGuildBankTab()) ~= false then
destBag = GetCurrentGuildBankTab()
end
end
else
destBag = findExistingStack(itemLink, bankType, min(have, need))
if not destBag then
if next(GetEmptySlots(bankType)) ~= nil then
destBag = canGoInBag(itemString, getContainerTable(bankType))
end
end
end
if destBag then
if have > need then
tinsert(splitMoves, { src = "bags", bag = bag, slot = slot, quantity = need })
bagMoves[itemString] = nil
else
tinsert(fullMoves, { src = "bags", bag = bag, slot = slot, quantity = have })
bagMoves[itemString] = bagMoves[itemString] - have
if bagMoves[itemString] <= 0 then
bagMoves[itemString] = nil
end
end
else
bankFull = true
end
end
end
end
end
end
end
end
if next(bankMoves) ~= nil then -- generate moves from bank to bags
setSrcBagFunctions(bankType)
setDestBagFunctions("bags")
for item, _ in pairs(bankMoves) do
for i, bag in ipairs(getContainerTable(bankType)) do
for slot = 1, TSM.getContainerNumSlotsSrc(bag) do
local itemLink = TSM.getContainerItemLinkSrc(bag, slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
if itemString and itemString == item then
local have = TSM.getContainerItemQty(bag, slot)
local need = bankMoves[itemString]
if have and need then
if not TSMAPI:IsSoulbound(bag, slot) or includeSoulbound then
local destBag = findExistingStack(itemLink, "bags", min(have, need)) or canGoInBag(itemString, getContainerTable("bags"))
if destBag then
if have > need then
tinsert(splitMoves, { src = bankType, bag = bag, slot = slot, quantity = need })
bankMoves[itemString] = nil
else
tinsert(fullMoves, { src = bankType, bag = bag, slot = slot, quantity = have })
bankMoves[itemString] = bankMoves[itemString] - have
if bankMoves[itemString] <= 0 then
bankMoves[itemString] = nil
end
end
else
bagsFull = true
end
end
end
end
end
end
end
end
if next(fullMoves) ~= nil then
if bankType == "guildbank" then
TSMAPI:CreateTimeDelay("moveItem", 0.05, TSM.moveItem, 0.35)
else
TSMAPI:CreateTimeDelay("moveItem", 0.05, TSM.moveItem, 0.05)
end
elseif next(splitMoves) ~= nil then
if bankType == "guildbank" then
TSMAPI:CreateTimeDelay("moveSplitItem", 0.05, TSM.moveSplitItem, 0.75)
else
TSMAPI:CreateTimeDelay("moveSplitItem", 0.05, TSM.moveSplitItem, 0.4)
end
else
if bagsFull and not bankFull then
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - Bags are full"])
end
elseif bankFull and not bagsFull then
for _, callback in ipairs(callbackMsg) do
if bankType == "guildbank" then
callback(L["Cancelled - Guildbank is full"])
elseif bankType == "bank" then
callback(L["Cancelled - Bank is full"])
else
callback("Cancelled - " .. bankType .. " is full")
end
end
elseif bagsFull and bankFull then
for _, callback in ipairs(callbackMsg) do
if bankType == "guildbank" then
callback(L["Cancelled - Bags and guildbank are full"])
elseif bankType == "bank" then
callback(L["Cancelled - Bags and bank are full"])
else
callback("Cancelled - Bags and " .. bankType .. " are full")
end
end
else
for _, callback in ipairs(callbackMsg) do
callback(L["Done"])
end
end
wipe(callbackMsg)
end
end
function TSM.moveItem()
if not TSM:areBanksVisible() then
wipe(fullMoves)
wipe(splitMoves)
TSMAPI:CancelFrame("moveItem")
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - You must be at a bank or guildbank"])
end
wipe(callbackMsg)
return
end
local next = next
if #fullMoves > 0 then
local i = next(fullMoves)
if fullMoves[i].src == "bags" then
setSrcBagFunctions("bags")
setDestBagFunctions(bankType)
local itemString = TSMAPI:GetBaseItemString(TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot), true)
local itemLink = TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot)
local have = TSM.getContainerItemQty(fullMoves[i].bag, fullMoves[i].slot)
local need = fullMoves[i].quantity
if have and need then
if bankType == "guildbank" then
if findExistingStack(itemLink, bankType, need, true) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
elseif GetEmptySlotCount(GetCurrentGuildBankTab()) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
else
if findExistingStack(itemLink, bankType, need) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
elseif next(GetEmptySlots(bankType)) ~= nil and canGoInBag(itemString, getContainerTable(bankType)) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
end
else
setSrcBagFunctions(bankType)
setDestBagFunctions("bags")
local itemString = TSMAPI:GetBaseItemString(TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot), true)
local itemLink = TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot)
local have = TSM.getContainerItemQty(fullMoves[i].bag, fullMoves[i].slot)
local need = fullMoves[i].quantity
if have and need then
if findExistingStack(itemLink, "bags", need) then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= fullMoves[i].bag then
SetCurrentGuildBankTab(fullMoves[i].bag)
end
end
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
elseif next(GetEmptySlots("bags")) ~= nil and canGoInBag(itemString, getContainerTable("bags")) then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= fullMoves[i].bag then
SetCurrentGuildBankTab(fullMoves[i].bag)
end
end
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
end
tremove(fullMoves, i)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
function TSM.moveSplitItem()
if not TSM:areBanksVisible() then
wipe(fullMoves)
wipe(splitMoves)
TSMAPI:CancelFrame("moveSplitItem")
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - You must be at a bank or guildbank"])
end
wipe(callbackMsg)
return
end
local next = next
--if next(moves) ~= nil then
if #splitMoves > 0 then
local i = next(splitMoves)
if splitMoves[i].src == "bags" then
setSrcBagFunctions("bags")
setDestBagFunctions(bankType)
local itemLink = TSM.getContainerItemLinkSrc(splitMoves[i].bag, splitMoves[i].slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
local have = TSM.getContainerItemQty(splitMoves[i].bag, splitMoves[i].slot)
local need = splitMoves[i].quantity
if have and need then
local destBag, destSlot
destBag, destSlot = findExistingStack(itemLink, bankType, need)
if destBag and destSlot then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
local emptyBankSlots = GetEmptySlots(bankType)
destBag = canGoInBag(itemString, getContainerTable(bankType))
if emptyBankSlots[destBag] then
destSlot = emptyBankSlots[destBag][1]
end
if destBag and destSlot then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= destBag then
SetCurrentGuildBankTab(destBag)
end
end
if GetEmptySlotCount(destBag) then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
else
if next(GetEmptySlots(bankType)) ~= nil then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
end
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves)
end
else
setSrcBagFunctions(bankType)
setDestBagFunctions("bags")
local itemLink = TSM.getContainerItemLinkSrc(splitMoves[i].bag, splitMoves[i].slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
local have = TSM.getContainerItemQty(splitMoves[i].bag, splitMoves[i].slot)
local need = splitMoves[i].quantity
if have and need then
local destBag, destSlot
destBag, destSlot = findExistingStack(itemLink, "bags", need)
if destBag and destSlot then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
local emptyBagSlots = GetEmptySlots("bags")
destBag = canGoInBag(itemString, getContainerTable("bags"))
if emptyBagSlots[destBag] then
destSlot = emptyBagSlots[destBag][1]
end
if destBag and destSlot then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= splitMoves[i].bag then
SetCurrentGuildBankTab(splitMoves[i].bag)
end
end
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
end
end
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves)
end
end
tremove(splitMoves, i)
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves)
end
end
function TSMAPI:MoveItems(requestedItems, callback, includeSoulbound)
wipe(bagState)
if callback then
assert(type(callback) == "function", format("Expected function, got %s.", type(callback)))
tinsert(callbackMsg, callback)
end
bagState = getTotalItems("bags") -- create initial bagstate
-- iterates over the requested items and adjusts bagState quantities , negative removes from bagState, positive adds to bagState
-- this gives the final states to generate the moves from
for itemString, qty in pairs(requestedItems) do
if not bagState[itemString] then bagState[itemString] = 0
end
bagState[itemString] = bagState[itemString] + qty
if bagState[itemString] < 0 then
bagState[itemString] = 0
end
end
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves(includeSoulbound))
end
function TSM:areBanksVisible()
if BagnonFrameguildbank and BagnonFrameguildbank:IsVisible() then
return true
elseif BagnonFramebank and BagnonFramebank:IsVisible() then
return true
elseif GuildBankFrame and GuildBankFrame:IsVisible() then
return true
elseif BankFrame and BankFrame:IsVisible() then
return true
elseif (ARKINV_Frame4 and ARKINV_Frame4:IsVisible()) or (ARKINV_Frame3 and ARKINV_Frame3:IsVisible()) then
return true
elseif (BagginsBag8 and BagginsBag8:IsVisible()) or (BagginsBag9 and BagginsBag9:IsVisible()) or (BagginsBag10 and BagginsBag10:IsVisible()) or (BagginsBag11 and BagginsBag11:IsVisible()) or (BagginsBag12 and BagginsBag12:IsVisible()) then
return true
elseif (CombuctorFrame2 and CombuctorFrame2:IsVisible()) then
return true
elseif (BaudBagContainer2_1 and BaudBagContainer2_1:IsVisible()) then
return true
elseif (AdiBagsContainer2 and AdiBagsContainer2:IsVisible()) then
return true
elseif (OneBankFrame and OneBankFrame:IsVisible()) then
return true
elseif (EngBank_frame and EngBank_frame:IsVisible()) then
return true
elseif (TBnkFrame and TBnkFrame:IsVisible()) then
return true
elseif (famBankFrame and famBankFrame:IsVisible()) then
return true
elseif (LUIBank and LUIBank:IsVisible()) then
return true
elseif (ElvUI_BankContainerFrame and ElvUI_BankContainerFrame:IsVisible()) then
return true
elseif (TukuiBank and TukuiBank:IsShown()) then
return true
elseif (AdiBagsContainer1 and AdiBagsContainer1.isBank and AdiBagsContainer1:IsVisible()) or (AdiBagsContainer2 and AdiBagsContainer2.isBank and AdiBagsContainer2:IsVisible()) then
return true
elseif BagsFrameBank and BagsFrameBank:IsVisible() then
return true
elseif AspUIBank and AspUIBank:IsVisible() then
return true
elseif NivayacBniv_Bank and NivayacBniv_Bank:IsVisible() then
return true
elseif DufUIBank and DufUIBank:IsVisible() then
return true
end
return nil
end

1183
TradeSkillMaster/Core/Options.lua

File diff suppressed because it is too large Load Diff

458
TradeSkillMaster/Core/Prices.lua

@ -0,0 +1,458 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains price related TSMAPI functions.
local TSM = select(2, ...)
local moduleObjects = TSM.moduleObjects
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
TSM_PRICE_TEMP = {loopError=function(str) TSM:Printf(L["Loop detected in the following custom price:"].." "..TSMAPI.Design:GetInlineColor("link")..str.."|r") end}
local MONEY_PATTERNS = {
"([0-9]+g[ ]*[0-9]+s[ ]*[0-9]+c)", -- g/s/c
"([0-9]+g[ ]*[0-9]+s)", -- g/s
"([0-9]+g[ ]*[0-9]+c)", -- g/c
"([0-9]+s[ ]*[0-9]+c)", -- s/c
"([0-9]+g)", -- g
"([0-9]+s)", -- s
"([0-9]+c)", -- c
}
local MATH_FUNCTIONS = {
["avg"] = "_avg",
["min"] = "_min",
["max"] = "_max",
["first"] = "_first",
["check"] = "_check",
}
function TSMAPI:GetPriceSources()
local sources = {}
for _, obj in pairs(moduleObjects) do
if obj.priceSources then
for _, info in ipairs(obj.priceSources) do
sources[info.key] = info.label
end
end
end
return sources
end
local itemValueKeyCache = {}
function TSMAPI:GetItemValue(link, key)
local itemLink = select(2, TSMAPI:GetSafeItemInfo(link)) or link
if not itemLink then return end
-- look in module objects for this key
if itemValueKeyCache[key] then
local info = itemValueKeyCache[key]
return info.callback(itemLink, info.arg)
end
for _, obj in pairs(moduleObjects) do
if obj.priceSources then
for _, info in ipairs(obj.priceSources) do
if info.key == key then
itemValueKeyCache[key] = info
local value = info.callback(itemLink, info.arg)
return (type(value) == "number" and value > 0) and value or nil
end
end
end
end
end
-- validates a price string that was passed into TSMAPI:ParseCustomPrice
local supportedOperators = { "+", "-", "*", "/" }
local function ParsePriceString(str, badPriceSource)
if tonumber(str) then
return function() return tonumber(str) end
end
local origStr = str
-- make everything lower case
str = strlower(str)
-- remove any colors around gold/silver/copper
str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])g|r", "g")
str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])s|r", "s")
str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])c|r", "c")
-- replace all formatted gold amount with their copper value
local start = 1
local goldAmountContinue = true
while goldAmountContinue do
goldAmountContinue = false
local minFind = {}
for _, pattern in ipairs(MONEY_PATTERNS) do
local s, e, sub = strfind(str, pattern, start)
if s and (not minFind.s or minFind.s > s) then
minFind.s = s
minFind.e = e
minFind.sub = sub
end
end
if minFind.s then
local value = TSMAPI:UnformatTextMoney(minFind.sub)
if not value then return end -- sanity check
local preStr = strsub(str, 1, minFind.s-1)
local postStr = strsub(str, minFind.e+1)
str = preStr .. value .. postStr
start = #str - #postStr + 1
goldAmountContinue = true
end
end
-- remove up to 1 occurance of convert(priceSource[, item])
local convertPriceSource, convertItem
local _, _, convertParams = strfind(str, "convert%(([^%)]+)%)")
if convertParams then
local source
local s = strfind(convertParams, "|c")
if s then
local _, e = strfind(convertParams, "|r")
local itemString = e and TSMAPI:GetItemString(strsub(convertParams, s, e))
if not itemString then return nil, L["Invalid item link."] end -- there's an invalid item link in the convertParams
convertItem = itemString
source = strsub(convertParams, 1, s - 1)
elseif strfind(convertParams, "item:") then
local s, e = strfind(convertParams, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
convertItem = strsub(convertParams, s, e)
source = strsub(convertParams, 1, s - 1)
elseif strfind(convertParams, "battlepet:") then
local s, e = strfind(convertParams, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
convertItem = strsub(convertParams, s, e)
source = strsub(convertParams, 1, s - 1)
else
source = convertParams
end
source = gsub(source:trim(), ",$", ""):trim()
for key in pairs(TSMAPI:GetPriceSources()) do
if strlower(key) == source then
convertPriceSource = key
break
end
end
if not convertPriceSource then
return nil, L["Invalid price source in convert."]
end
local num = 0
str, num = gsub(str, "convert%(([^%)]+)%)", "~convert~")
if num > 1 then
return nil, L["A maximum of 1 convert() function is allowed."]
end
end
-- replace all item links with "~item~"
local items = {}
while true do
local s = strfind(str, "|c")
if not s then break end -- no more item links
local _, e = strfind(str, "|r")
local itemString = e and TSMAPI:GetItemString(strsub(str, s, e))
if not itemString then return nil, L["Invalid item link."] end -- there's an invalid item link in the str
tinsert(items, itemString)
str = strsub(str, 1, s - 1) .. "~item~" .. strsub(str, e + 1)
end
-- replace all itemStrings with "~item~"
while true do
local s, e
if strfind(str, "item:") then
s, e = strfind(str, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
elseif strfind(str, "battlepet:") then
s, e = strfind(str, "battlepet:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
else
break
end
local itemString = strsub(str, s, e)
tinsert(items, itemString)
str = strsub(str, 1, s - 1) .. "~item~" .. strsub(str, e + 1)
end
-- make sure there's spaces on either side of operators
for _, operator in ipairs(supportedOperators) do
str = gsub(str, TSMAPI:StrEscape(operator), " " .. operator .. " ")
end
-- add space to start of string for percentage matching
str = " "..str
-- fix any whitespace issues around item links and remove parenthesis
str = gsub(str, "%(([ ]*)~item~([ ]*)%)", " ~item~")
-- ensure a space on either side of parenthesis and commas
str = gsub(str, "%(", " ( ")
str = gsub(str, "%)", " ) ")
str = gsub(str, ",", " , ")
-- remove any occurances of more than one consecutive space
str = gsub(str, "([ ]+)", " ")
-- ensure equal number of left/right parenthesis
if select(2, gsub(str, "%(", "")) ~= select(2, gsub(str, "%)", "")) then return nil, L["Unbalanced parentheses."] end
-- convert percentages to decimal numbers
for leading, pctValue in gmatch(str, "([^0-9%.])([0-9%.]+)%%") do
if tonumber(pctValue) then
local number = tonumber(pctValue) / 100
str = gsub(str, leading..pctValue.."%%", leading .. number .. " *")
end
end
-- create array of valid price sources
local priceSourceKeys = {}
for key in pairs(TSMAPI:GetPriceSources()) do
tinsert(priceSourceKeys, strlower(key))
end
for key in pairs(TSM.db.global.customPriceSources) do
tinsert(priceSourceKeys, strlower(key))
end
-- validate all words in the string
local parts = TSMAPI:SafeStrSplit(str:trim(), " ")
for i, word in ipairs(parts) do
if tContains(supportedOperators, word) then
if i == #parts then
return nil, L["Invalid operator at end of custom price."]
end
-- valid operand
elseif badPriceSource == word then
-- price source that's explicitly invalid
return nil, format(L["You cannot use %s as part of this custom price."], word)
elseif tContains(priceSourceKeys, word) then
-- valid price source
elseif tonumber(word) then
-- make sure it's not an itemID (incorrect)
if i > 2 and parts[i-1] == "(" and tContains(priceSourceKeys, parts[i-2]) then
return nil, L["Invalid parameter to price source."]
end
-- valid number
elseif word == "~item~" then
-- make sure previous word was a price source
if i > 1 and tContains(priceSourceKeys, parts[i-1]) then
-- valid item parameter
else
return nil, L["Item links may only be used as parameters to price sources."]
end
elseif word == "(" or word == ")" then
-- valid parenthesis
elseif word == "," then
if not parts[i+1] or parts[i+1] == ")" then
return nil, L["Misplaced comma"]
else
-- we're hoping this is a valid comma within a function, will be caught by loadstring otherwise
end
elseif MATH_FUNCTIONS[word] then
if not parts[i+1] or parts[i+1] ~= "(" then
return nil, format(L["Invalid word: '%s'"], word)
end
-- valid math function
elseif word == "~convert~" then
-- valid convert statement
elseif word:trim() == "" then
-- harmless extra spaces
else
return nil, format(L["Invalid word: '%s'"], word)
end
end
for key in pairs(TSMAPI:GetPriceSources()) do
-- replace all "<priceSource> ~item~" occurances with the parameters to TSMAPI:GetItemValue (with "~item~" left in for the item)
for match in gmatch(" "..str.." ", " "..strlower(key).." ~item~") do
match = match:trim()
str = gsub(str, match, "(\"~item~\",\"" .. key .. "\",\"reg\")")
end
-- replace all "<priceSource>" occurances with the parameters to TSMAPI:GetItemValue (with _item for the item)
for match in gmatch(" "..str.." ", " "..strlower(key).." ") do
match = match:trim()
str = gsub(str, match, "(\"_item\",\"" .. key .. "\",\"reg\")")
end
end
for key in pairs(TSM.db.global.customPriceSources) do
-- price sources need to have at least 1 capital letter for this algorithm to work, so temporarily give it one
key = strupper(strsub(key, 1, 1))..strsub(key, 2)
-- replace all "<customPriceSource> ~item~" occurances with the parameters to TSMAPI:GetCustomPriceSourceValue (with "~item~" left in for the item)
for match in gmatch(" "..str.." ", " " .. strlower(key) .. " ~item~") do
match = match:trim()
str = gsub(str, match, "(\"~item~\",\"" .. key .. "\",\"custom\")")
end
-- replace all "<customPriceSource>" occurances with the parameters to TSMAPI:GetCustomPriceSourceValue (with _item for the item)
for match in gmatch(" "..str.." ", " " .. strlower(key) .. " ") do
match = match:trim()
str = gsub(str, match, "(\"_item\",\"" .. key .. "\",\"custom\")")
end
-- change custom price sources back to lower case
str = gsub(str, TSMAPI:StrEscape("(\"~item~\",\"" .. key .. "\",\"custom\")"), strlower("(\"~item~\",\"" .. key .. "\",\"custom\")"))
str = gsub(str, TSMAPI:StrEscape("(\"_item\",\"" .. key .. "\",\"custom\")"), strlower("(\"_item\",\"" .. key .. "\",\"custom\")"))
end
-- replace all occurances of "~item~" with the item link
for match in gmatch(str, "~item~") do
local itemString = tremove(items, 1)
if not itemString then return nil, L["Wrong number of item links."] end
str = gsub(str, match, itemString, 1)
end
-- replace any itemValue API calls with a lookup in the 'values' array
local itemValues = {}
for match in gmatch(str, "%(\"([^%)]+)%)") do
local index = #itemValues + 1
itemValues[index] = "{\"" .. match .. "}"
str = gsub(str, TSMAPI:StrEscape("(\"" .. match .. ")"), "values[" .. index .. "]")
end
-- replace "~convert~" appropriately
if convertPriceSource then
tinsert(itemValues, "{\"" .. (convertItem or "_item") .. "\",\"convert\",\"" .. convertPriceSource .. "\"}")
str = gsub(str, "~convert~", "values[" .. #itemValues .. "]")
end
-- replace math functions special custom function names
for word, funcName in pairs(MATH_FUNCTIONS) do
str = gsub(str, word, funcName)
end
-- remove any unused values
for i in ipairs(itemValues) do
if not strfind(" "..str.." ", " values%["..i.."%] ") then
itemValues[i] = "{}"
end
end
-- finally, create and return the function
local funcTemplate = [[
return function(_item)
local NAN = math.huge*0
local origStr = %s
local isTop
if not TSM_PRICE_TEMP.num then
TSM_PRICE_TEMP.num = 0
isTop = true
end
TSM_PRICE_TEMP.num = TSM_PRICE_TEMP.num + 1
if TSM_PRICE_TEMP.num > 100 then
if (TSM_PRICE_TEMP.lastPrint or 0) + 1 < time() then
TSM_PRICE_TEMP.lastPrint = time()
TSM_PRICE_TEMP.loopError(origStr)
end
return
end
local function isNAN(num)
return tostring(num) == tostring(NAN)
end
local function _min(...)
local nums = {...}
for i=#nums, 1, -1 do
if isNAN(nums[i]) then
tremove(nums, i)
end
end
if #nums == 0 then return NAN end
return min(unpack(nums))
end
local function _max(...)
local nums = {...}
for i=#nums, 1, -1 do
if isNAN(nums[i]) then
tremove(nums, i)
end
end
if #nums == 0 then return NAN end
return max(unpack(nums))
end
local function _first(...)
local nums = {...}
for i=1, #nums do
if type(nums[i]) == "number" and not isNAN(nums[i]) then
return nums[i]
end
end
return NAN
end
local function _avg(...)
local nums = {...}
local total, count = 0, 0
for i=#nums, 1, -1 do
if type(nums[i]) == "number" and not isNAN(nums[i]) then
total = total + nums[i]
count = count + 1
end
end
if count == 0 then return NAN end
return floor(total / count + 0.5)
end
local function _check(...)
if select('#', ...) > 3 then return end
local check, ifValue, elseValue = ...
check = check or NAN
ifValue = ifValue or NAN
elseValue = elseValue or NAN
return check > 0 and ifValue or elseValue
end
local values = {}
for i, params in ipairs({%s}) do
local itemString, key, extraParam = unpack(params)
if itemString then
itemString = (itemString == "_item") and _item or itemString
if key == "convert" then
values[i] = TSMAPI:GetConvertCost(itemString, extraParam)
elseif extraParam == "custom" then
values[i] = TSMAPI:GetCustomPriceSourceValue(itemString, key)
else
values[i] = TSMAPI:GetItemValue(itemString, key)
end
values[i] = values[i] or NAN
end
end
local result = floor((%s) + 0.5)
if TSM_PRICE_TEMP.num then
TSM_PRICE_TEMP.num = TSM_PRICE_TEMP.num - 1
end
if isTop then
TSM_PRICE_TEMP.num = nil
end
return not isNAN(result) and result or nil
end
]]
local func, loadErr = loadstring(format(funcTemplate, "\""..origStr.."\"", table.concat(itemValues, ","), str))
if loadErr then
loadErr = gsub(loadErr:trim(), "([^:]+):.", "")
return nil, L["Invalid function."].." Details: "..loadErr
end
local success, func = pcall(func)
if not success then return nil, L["Invalid function."] end
return func
end
local customPriceCache = {}
local badCustomPriceCache = {}
function TSMAPI:ParseCustomPrice(priceString, badPriceSource)
priceString = strlower(tostring(priceString):trim())
if priceString == "" then return nil, L["Empty price string."] end
if badCustomPriceCache[priceString] then return nil, badCustomPriceCache[priceString] end
if customPriceCache[priceString] then return customPriceCache[priceString] end
local func, err = ParsePriceString(priceString, badPriceSource)
if err then
badCustomPriceCache[priceString] = err
return nil, err
end
customPriceCache[priceString] = func
return func
end
function TSMAPI:GetCustomPriceSourceValue(itemString, key)
local source = TSM.db.global.customPriceSources[key]
if not source then return end
local func = TSMAPI:ParseCustomPrice(source)
if not func then return end
return func(itemString)
end

205
TradeSkillMaster/Core/Sync.lua

@ -0,0 +1,205 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file handled multi-account syncing and communication
-- register this file with Ace libraries
local TSM = select(2, ...)
local Sync = TSM:NewModule("Sync", "AceComm-3.0", "AceEvent-3.0")
TSMAPI.Sync = {}
local private = {callbacks={}, addedFriends={}, invalidPlayers={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Sync_private")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
-- Request friend info from the server
ShowFriends()
function Sync:OnEnable()
Sync:RegisterComm("TSMSyncData")
Sync:RegisterEvent("CHAT_MSG_SYSTEM")
local data = {characters={}, accountKey=TSMAPI.Sync:GetAccountKey()}
for name in pairs(TSM.db.factionrealm.characters) do
data.characters[name] = TSMAPI.Sync:GetAccountKey()
end
TSMAPI:CreateTimeDelay("syncSetupDelay", 3, function() TSMAPI.Sync:BroadcastData("TradeSkillMaster", "SETUP", data) end)
end
--Load the libraries
local libS = LibStub:GetLibrary("AceSerializer-3.0")
local libC = LibStub:GetLibrary("LibCompress")
local libCE = libC:GetAddonEncodeTable()
function Sync:OnCommReceived(_, data, _, source)
-- remove realm name from source
source = ("-"):split(source)
-- Decode the compressed data
data = libCE:Decode(data)
-- Decompress the decoded data
local data = libC:Decompress(data)
if not data then return end
-- Deserialize the decompressed data
local success
local tmpData = data
success, data = libS:Deserialize(data)
if not success or not data then return end
-- data is good, do callback
local key = data.__key
local module = data.__module
local account = data.__account
if not key or not module or not private.callbacks[module] then return end
data.__key = nil
data.__module = nil
data.__account = nil
-- make sure we are getting this from a known source
if not TSM.db.factionrealm.syncAccounts[account] and (module ~= "TradeSkillMaster" and not data.isSetup) then return end
private.callbacks[module](key, data, source)
end
function Sync:CHAT_MSG_SYSTEM(_, msg)
if #private.addedFriends == 0 then return end
if msg == ERR_FRIEND_NOT_FOUND then
tremove(private.addedFriends, 1)
else
for i, v in ipairs(private.addedFriends) do
if format(ERR_FRIEND_ADDED_S, v) == msg then
tremove(private.addedFriends, i)
private.invalidPlayers[strlower(v)] = true
end
end
end
end
function TSMAPI.Sync:GetAccountKey()
return TSM.db.factionrealm.accountKey
end
function TSM:RegisterSyncCallback(module, callback)
private.callbacks[module] = callback
end
function private:IsPlayerOnline(target, noAdd)
for i=1, GetNumFriends() do
local name, _, _, _, connected = GetFriendInfo(i)
if name and strlower(name) == strlower(target) then
return connected
end
end
if not noAdd and not private.invalidPlayers[strlower(target)] and GetNumFriends() ~= 50 then
-- add them as a friend
AddFriend(target)
tinsert(private.addedFriends, target)
for i=1, GetNumFriends() do
local name, _, _, _, connected = GetFriendInfo(i)
if name and strlower(name) == strlower(target) then
return connected
end
end
end
end
function TSMAPI.Sync:SendData(module, key, data, target)
local playerOnline = private:IsPlayerOnline(target)
if not playerOnline then return end
data.__module = module
data.__key = key
data.__account = TSMAPI.Sync:GetAccountKey()
-- we will encode using Huffman, LZW, and no compression separately
-- this is to deal with a bug in the compression code
local orig = data
local serialized = libS:Serialize(data)
local encodedData = {}
encodedData[1] = libCE:Encode(libC:CompressHuffman(serialized))
encodedData[2] = libCE:Encode(libC:CompressLZW(serialized))
encodedData[3] = libCE:Encode("\001"..serialized)
-- verify each compresion and pick the shorted valid one
local minIndex = -1
local minLen = math.huge
for i=#encodedData, 1, -1 do
local test = libC:Decompress(libCE:Decode(encodedData[i]))
if test and test == serialized and #encodedData[i] < minLen then
minLen = #encodedData[i]
minIndex = i
end
end
-- sanity check
if not encodedData[minIndex] then error("Could not encode data.") end
-- send off the serialized, compressed, and encoded data
Sync:SendCommMessage("TSMSyncData", encodedData[minIndex], "WHISPER", target)
end
function TSMAPI.Sync:BroadcastData(module, key, data)
for account, players in pairs(TSM.db.factionrealm.syncAccounts) do
if account ~= TSMAPI.Sync:GetAccountKey() then
local sent
for player in pairs(players) do
if private:IsPlayerOnline(player, true) then
TSMAPI.Sync:SendData(module, key, data, player)
sent = true
break
end
end
if not sent then
for player in pairs(players) do
if private:IsPlayerOnline(player) then
TSMAPI.Sync:SendData(module, key, data, player)
sent = true
break
end
end
end
end
end
end
function private:SendSetupData(target, isResponse, isSetup)
local data = {isResponse=isResponse, isSetup=isSetup, characters={}, accountKey=TSMAPI.Sync:GetAccountKey()}
for name in pairs(TSM.db.factionrealm.characters) do
data.characters[name] = true
end
TSMAPI.Sync:SendData("TradeSkillMaster", "SETUP", data, target)
end
function TSM:DoSyncSetup(target)
if target == "" then
private.syncSetupTarget = nil
return
end
private.syncSetupTarget = target
private:SendSetupData(target, nil, true)
end
function TSM:SyncCallback(key, data, source)
if key == "SETUP" then
if (data.isSetup and strlower(source) ~= strlower(private.syncSetupTarget or "")) or (not data.isSetup and not TSM.db.factionrealm.syncAccounts[data.accountKey]) then
return
end
TSMAPI:Verify(data.accountKey ~= TSMAPI.Sync:GetAccountKey(), "It appears that you've manually copied your saved variables between accounts which will cause TSM's automatic sync'ing to not work. You'll need to undo this, and/or delete the TradeSkillMaster, TSM_Crafting, and TSM_ItemTracker saved variables files on both accounts (with WoW closed) in order to fix this.")
TSM.db.factionrealm.syncAccounts[data.accountKey] = data.characters
if data.isSetup then
TSMAPI:CloseFrame()
TSM:Printf(L["Setup account sync'ing with the account which '%s' is on."], source)
end
if data.isResponse then return end -- already responded
private:SendSetupData(source, true, data.isSetup)
end
end

21
TradeSkillMaster/Core/Templates.xml

@ -0,0 +1,21 @@
<Ui>
<Frame name="TSMErrorHandlerTemplate" virtual="true">
<Scripts>
<OnLoad>
self.handler = function(...)
local msg = LibStub("AceAddon-3.0"):GetAddon("TradeSkillMaster"):IsValidError(...)
if msg then
local status, ret = pcall(self.errorHandler, msg, ...)
if status then
return ret
else
self.origErrorHandler(...)
end
elseif self.origErrorHandler then
return self.origErrorHandler(...)
end
end
</OnLoad>
</Scripts>
</Frame>
</Ui>

113
TradeSkillMaster/Core/Threading.lua

@ -0,0 +1,113 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for running stuff in a pseudo-thread
local TSM = select(2, ...)
local private = {frames={}, threads={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Threading_private")
TSMAPI.Threading = {}
local STATUS_YIELD = {}
local STATUS_DEAD = {}
local MAX_QUANTUM = 50
local ThreadPrototype = {
Resume = function(self, runTime)
self._endTime = debugprofilestop() + runTime
local noErr, status = coroutine.resume(self._co, self, self._param)
if noErr then
return status
else
-- throw error in delay so we don't kill this execution path
TSMAPI:CreateTimeDelay(0, function() error(status) end)
return STATUS_DEAD
end
end,
Yield = function(self, force)
local currentTime = debugprofilestop()
if force or currentTime > self._endTime then
coroutine.yield(STATUS_YIELD)
end
end,
Sleep = function(self, seconds)
self._sleeping = seconds
coroutine.yield(STATUS_YIELD)
end,
}
local quantums = {}
function private.RunScheduler(_, elapsed)
-- deal with sleeping threads and try and assign requested quantums
local totalTime = min(elapsed * 1000, MAX_QUANTUM)
local usedTime = 0
for i, thread in ipairs(private.threads) do
if thread._sleeping then
thread._sleeping = thread._sleeping - elapsed
if thread._sleeping <= 0 then
thread._sleeping = nil
end
end
quantums[i] = thread._sleeping and 0 or (thread._percent * totalTime)
usedTime = usedTime + quantums[i]
end
-- scale everything down if the total is > the total time
if usedTime > totalTime then
for i=1, #quantums do
quantums[i] = quantums[i] * (totalTime / usedTime)
end
end
-- run the threads that aren't sleeping
for i, thread in ipairs(private.threads) do
if quantums[i] > 0 then
-- resume running for a quantum based on its percent
local status = thread:Resume(quantums[i])
if status ~= STATUS_YIELD then
tremove(private.threads, i)
if thread._callback and status ~= STATUS_DEAD then thread._callback() end
end
end
end
end
function TSMAPI.Threading:Start(func, percent, callback, param)
local thread = CopyTable(ThreadPrototype)
thread._co = coroutine.create(func)
thread._percent = percent
thread._callback = callback
thread._param = param
thread._sleeping = nil
tinsert(private.threads, thread)
end
do
local frame = CreateFrame("Frame")
frame:SetScript("OnUpdate", private.RunScheduler)
end
-- -- EXAMPLE USAGE:
-- local function TestFunc(self, letter)
-- for i = 1, 10 do
-- self:Sleep(1)
-- print(letter, i)
-- if letter == "B" and i == 10 then print(_G[i].nonExistant) end
-- end
-- end
-- function TSMTest()
-- local start = GetTime()
-- TSMAPI.Threading:Start(TestFunc, 1, function() print("DONE", GetTime()-start) end, "A")
-- TSMAPI.Threading:Start(TestFunc, 1, function() print("DONE", GetTime()-start) end, "B")
-- end

300
TradeSkillMaster/Core/Tooltips.lua

@ -0,0 +1,300 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains all the code for the new tooltip options
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local lib = TSMAPI
local tooltipLib = LibStub("LibExtraTip-1")
local moduleObjects = TSM.moduleObjects
local moduleNames = TSM.moduleNames
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Tooltips_private")
private.tooltipInfo = {}
-- **************************************************************************
-- LibExtraTip Functions
-- **************************************************************************
function TSM:SetupTooltips()
-- tooltipLib:AddCallback({type = "battlepet", callback = private.LoadTooltip})
tooltipLib:AddCallback({type = "item", callback = private.LoadTooltip})
tooltipLib:RegisterTooltip(GameTooltip)
tooltipLib:RegisterTooltip(ItemRefTooltip)
-- tooltipLib:RegisterTooltip(BattlePetTooltip)
local orig = OpenMailAttachment_OnEnter
OpenMailAttachment_OnEnter = function(self, index)
private.lastMailTooltipUpdate = private.lastMailTooltipUpdate or 0
if private.lastMailTooltipIndex ~= index or private.lastMailTooltipUpdate + 0.1 < GetTime() then
private.lastMailTooltipUpdate = GetTime()
private.lastMailTooltipIndex = index
orig(self, index)
end
end
end
local tooltipLines = {lastUpdate = 0, modifier=0}
local function GetTooltipLines(itemString, quantity)
local modifier = (IsShiftKeyDown() and 4 or 0) + (IsAltKeyDown() and 2 or 0) + (IsControlKeyDown() and 1 or 0)
if modifier ~= tooltipLines.modifier then
tooltipLines.modifier = modifier
tooltipLines.lastUpdate = 0
end
if tooltipLines.itemString ~= itemString or tooltipLines.quantity ~= quantity or (tooltipLines.lastUpdate + 0.5) < GetTime() then
wipe(tooltipLines)
for _, moduleName in ipairs(moduleNames) do
if moduleObjects[moduleName].GetTooltip then
local moduleLines = moduleObjects[moduleName]:GetTooltip(itemString, quantity)
if type(moduleLines) ~= "table" then moduleLines = {} end
for _, line in ipairs(moduleLines) do
tinsert(tooltipLines, line)
end
end
end
tooltipLines.itemString = itemString
tooltipLines.quantity = quantity
tooltipLines.lastUpdate = GetTime()
end
return tooltipLines
end
function private.LoadTooltip(tipFrame, link, quantity)
local itemString = TSMAPI:GetItemString(link)
if not itemString then return end
local lines = GetTooltipLines(itemString, quantity)
if #lines > 0 then
tooltipLib:AddLine(tipFrame, " ", 1, 1, 0, TSM.db.profile.embeddedTooltip)
local r, g, b = unpack(TSM.db.profile.design.inlineColors.tooltip or { 130, 130, 250 })
for i = 1, #lines do
if type(lines[i]) == "table" then
tooltipLib:AddDoubleLine(tipFrame, lines[i].left, lines[i].right, r / 255, g / 255, b / 255, r / 255, g / 255, b / 255, TSM.db.profile.embeddedTooltip)
else
tooltipLib:AddLine(tipFrame, lines[i], r / 255, g / 255, b / 255, TSM.db.profile.embeddedTooltip)
end
end
tooltipLib:AddLine(tipFrame, " ", 1, 1, 0, TSM.db.profile.embeddedTooltip)
end
end
-- **************************************************************************
-- TSM Tooltip Options
-- **************************************************************************
function TSM:RegisterTooltipInfo(module, info)
info = CopyTable(info)
info.module = module
tinsert(private.tooltipInfo, info)
end
function TSMAPI:GetMoneyCoinsTooltip()
return TSM.db.profile.moneyCoinsTooltip
end
local loadTooltipOptionsTab
function TSM:LoadTooltipOptions(parent)
local tabs = {}
local next = next
for _, info in ipairs(private.tooltipInfo) do
tinsert(tabs, { text = info.module, value = info.module })
end
if next(tabs) then
sort(tabs, function(a, b)
return a.text < b.text
end)
end
tinsert(tabs, 1, { text = L["General"], value = "Help" })
local tabGroup = AceGUI:Create("TSMTabGroup")
tabGroup:SetLayout("Fill")
tabGroup:SetTabs(tabs)
tabGroup:SetCallback("OnGroupSelected", function(_, _, value)
tabGroup:ReleaseChildren()
if value == "Help" then
private:DrawTooltipHelp(tabGroup)
else
for _, info in ipairs(private.tooltipInfo) do
if info.module == value then
info.callback(tabGroup, loadTooltipOptionsTab and loadTooltipOptionsTab.tooltip)
end
end
end
end)
parent:AddChild(tabGroup)
tabGroup:SelectTab(loadTooltipOptionsTab and loadTooltipOptionsTab.module or "Help")
end
function private:DrawTooltipHelp(container)
local priceSources = TSMAPI:GetPriceSources()
priceSources["Crafting"] = nil
priceSources["VendorBuy"] = nil
priceSources["VendorSell"] = nil
priceSources["Disenchant"] = nil
local page = {
{
-- scroll frame to contain everything
type = "ScrollFrame",
layout = "List",
children = {
{
type = "InlineGroup",
layout = "flow",
title = L["General Options"],
children = {
{
type = "Label",
text = L["Display prices in tooltips as:"],
relativeWidth = 0.25,
},
{
type = "CheckBox",
label = L["Coins:"],
relativeWidth = 0.09,
settingInfo = {TSM.db.profile, "moneyCoinsTooltip"},
callback = function(_, _, value)
if value == true then
TSM.db.profile.moneyTextTooltip = false
end
container:ReloadTab()
end,
},
{
type = "Label",
relativeWidth = 0.22,
text = TSMAPI:FormatTextMoneyIcon(3451267, "|cffffffff", false, true),
},
{
type = "CheckBox",
label = L["Text:"],
relativeWidth = 0.09,
settingInfo = {TSM.db.profile, "moneyTextTooltip"},
callback = function(_, _, value)
if value == true then
TSM.db.profile.moneyCoinsTooltip = false
end
container:ReloadTab()
end,
},
{
type = "Label",
text = TSMAPI:FormatTextMoney(3451267, "|cffffffff", false, true),
},
{
type = "HeadingLine",
},
{
type = "CheckBox",
label = L["Embed TSM Tooltips"],
settingInfo = {TSM.db.profile, "embeddedTooltip"},
tooltip = L["If checked, TSM's tooltip lines will be embedded in the item tooltip. Otherwise, it will show as a separate box below the item's tooltip."],
},
{
type = "CheckBox",
label = L["Display Group / Operation Info in Tooltips"],
settingInfo = {TSM.db.profile, "tooltip"},
},
{
type = "CheckBox",
label = L["Display vendor buy price in tooltip."],
settingInfo = { TSM.db.profile, "vendorBuyTooltip" },
tooltip = L["If checked, the price of buying the item from a vendor is displayed."],
},
{
type = "CheckBox",
label = L["Display vendor sell price in tooltip."],
settingInfo = { TSM.db.profile, "vendorSellTooltip" },
tooltip = L["If checked, the price of selling the item to a vendor displayed."],
},
},
},
{
type = "InlineGroup",
layout = "flow",
title = L["Destroy Values"],
children = {
{
type = "Dropdown",
label = L["Destroy Value Source:"],
settingInfo = {TSM.db.profile, "destroyValueSource"},
list = priceSources,
relativeWidth = 0.5,
tooltip = L["Select the price source for calculating destroy values."],
},
{
type = "CheckBox",
label = L["Display Detailed Destroy Tooltips"],
settingInfo = { TSM.db.profile, "detailedDestroyTooltip" },
relativeWidth = 0.49,
tooltip = L["If checked, a detailed list of items which an item destroys into will be displayed below the destroy value in the tooltip."],
},
{
type = "HeadingLine",
},
{
type = "CheckBox",
label = L["Display mill value in tooltip."],
settingInfo = { TSM.db.profile, "millTooltip" },
relativeWidth = 0.5,
tooltip = L["If checked, the mill value of the item will be shown. This value is calculated using the average market value of materials the item will mill into."],
},
{
type = "CheckBox",
label = L["Display prospect value in tooltip."],
settingInfo = { TSM.db.profile, "prospectTooltip" },
relativeWidth = 0.5,
tooltip = L["If checked, the prospect value of the item will be shown. This value is calculated using the average market value of materials the item will prospect into."],
},
{
type = "CheckBox",
label = L["Display disenchant value in tooltip."],
settingInfo = { TSM.db.profile, "deTooltip" },
relativeWidth = 0.5,
tooltip = L["If checked, the disenchant value of the item will be shown. This value is calculated using the average market value of materials the item will disenchant into."],
},
},
},
},
},
}
if next(TSM.db.global.customPriceSources) then
local inlineGroup = {
type = "InlineGroup",
layout = "flow",
title = L["Custom Price Sources"],
children = {
{
type = "Label",
text = L["Custom price sources to display in item tooltips:"],
relativeWidth = 1,
},
},
}
for name in pairs(TSM.db.global.customPriceSources) do
local checkbox = {
type = "CheckBox",
label = name,
relativeWidth = 0.5,
settingInfo = { TSM.db.global.customPriceTooltips, name },
tooltip = L["If checked, this custom price will be displayed in item tooltips."],
}
tinsert(inlineGroup.children, checkbox)
end
tinsert(page[1].children, inlineGroup)
end
TSMAPI:BuildPage(container, page)
end

134
TradeSkillMaster/Data/ConnectedRealms.lua

@ -0,0 +1,134 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskillmaster_warehousing --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains various utility related to connected realms
local TSM = select(2, ...)
local lib = TSMAPI
local CONNECTED_REALMS = {
US = {
{"Aegwynn", "Bonechewer", "Daggerspine", "Gurubashi", "Hakkar"},
{"Agamaggan", "Archimode", "Jaedenar", "The Underbog"},
{"Akama", "Dragonmaw ", "Mug'thol"},
{"Aggramar", "Fizzcrank"},
{"Alexstrasza", "Terokkar"},
{"Alleria", "Khadgar"},
{"Altar of Storms", "Anetheron", "Magtheridon", "Ysondre"},
{"Andorhal", "Scilla", "Ursin"},
{"Antonidas", "Uldum"},
{"Anub'arak", "Chromaggus", "Crushridge", "Garithos", "Nathrezim", "Smolderthorn"},
{"Anvilmar", "Undermine"},
{"Arygos", "Llane"},
{"Auchindoun", "Cho'gall", "Laughing Skull"},
{"Azgalor", "Azshara", "Destromath", "Thunderlord"},
{"Azjol-Nerub", "Khaz Modan"},
{"Balnazzar", "Gorgonnash", "The Forgotten Coast", "Warsong"},
{"Black Dragonflight", "Gul'dan", "Skullcrusher"},
{"Blackhand", "Galakrond"},
{"Blackwing Lair", "Dethecus", "Detheroc", "Haomarush", "Lethon"},
{"Bladefist", "Kul Tiras"},
{"Blade's Edge", "Thunderhorn"},
{"Blood Furnace", "Mannoroth", "Nazjatar"},
{"Bloodscalp", "Boulderfist", "Dunemaul", "Maiev", "Stonemaul"},
{"Borean Tundra", "Shadowsong"},
{"Burning Blade", "Lightning's Blade", "Onyxia"},
{"Bronzebeard", "Shandris"},
{"Cairne", "Perenolde"},
{"Coilfang", "Dalvengyr", "Dark Iron", "Demon Soul"},
{"Darrowmere", "Windrunner"},
{"Dath'Remar", "Khaz'goroth"},
{"Dentarg", "Whisperwind"},
{"Draenor", "Echo Isles"},
{"Dragonblight", "Fenris"},
{"Drak'Tharon", "Firetree", "Malorne", "Rivendare", "Spirestone", "Stormscale"},
{"Drak'thul", "Skywall"},
{"Draka", "Suramar"},
{"Dreadmaul", "Thaurissan"},
{"Eitrigg", "Shu'halo"},
{"Eldre'Thalas", "Korialstrasz"},
{"Eonar", "Velen"},
{"Eredar", "Gorefiend", "Spinebreaker", "Wildhammer"},
{"Executus", "Kalecgos", "Shattered Halls"},
{"Exodar", "Medivh"},
{"Farstriders", "Silver Hand", "Thorium Brotherhood"},
{"Feathermoon", "Scarlet Crusade"},
{"Frostmane", "Ner'zhul", "Tortheldrin"},
{"Frostwolf", "Varshj"},
{"Ghostlands", "Kael'thas"},
{"Gundrak", "Jubei'Thos"},
{"Hellscream", "Zangarmarsh"},
{"Hydraxis", "Terenas"},
{"Icecrown", "Malygos"},
{"Kargath", "Norgannon"},
{"Kilrogg", "Winterhoof"},
{"Kirin Tor", "Sentinels", "Steamwheedle Cartel"},
{"Misha", "Rexxar"},
{"Mok'Nathal", "Silvermoon"},
{"Muradin", "Nordrassil"},
{"Nazgrel", "Nesingwary", "Vek'nilash"},
{"Quel'dorei", "Sen'jin"},
{"Runetotem", "Uther"},
{"Ravencrest", "Uldaman"},
},
EU = {
{"Aggra (Português)", "Grim Batol"},
{"Agamaggan", "Bloodscalp", "Crushridge", "Emeriss", "Hakkar"},
{"Ahn'Qiraj", "Balnazzar", "Boulderfist", "Chromaggus", "Daggerspine", "Laughing Skull", "Shattered Halls", "Sunstrider", "Talnivarr", "Trollbane"},
{"Alexstrasza", "Nethersturm"},
{"Anetheron", "Festung der Stürme", "Gul'dan", "Rajaxx"},
{"Arak-arahm", "Rashgarroth", "Throk'Feroth"},
{"Arathi", "Illidan", "Naxxramas", "Temple noir"},
{"Arthas", "Blutkessel", "Vek'lor"},
{"Auchindoun", "Dunemaul", "Jaedenar"},
{"Area 52", "Un'Goro"},
{"Bladefist", "Zenedar"},
{"Bloodfeathre", "Burning Steppes", "Executus", "Kor'gall", "Shattered Hand"},
{"Burning Blade", "Drak'thul"},
{"Cho'gall", "Eldre'Thalas", "Sinstralis"},
{"Colinas Pardas", "Los Errantes", "Tyrande"},
{"Conseil des Ombres", "Culte de la Rive noire", "La Croisade écarlate"},
{"Dalaran", "Marécage de Zangar"},
{"Dalvengyr", "Nazjatar"},
{"Darksorrow", "Genjuros", "Neptulon"},
{"Das Syndikat", "Der abyssiche Rat", "Die Arguswacht", "Die Todeskrallen"},
{"Deepholm", "Razuvious"},
{"Deathwing", "Karazhan", "Lightning's Blade"},
{"Dethecus", "Mug'thol", "Terrordar", "Theradras"},
{"Dragonmaw", "Haomarush", "Spinebreaker", "Stormreaver", "Vashj"},
{"Echsenkessel", "Mal'Ganis", "Taerar"},
{"Eitrigg", "Krasus"},
{"Elune", "Varimathras"},
{"Exodar", "Minahonda"},
{"Garona", "Ner'zhul"},
{"Garrosh", "Nozdormu", "Shattrath"},
{"Gilneas", "Ulduar"},
{"Kilrogg", "Nagrand", "Runetotem"},
{"Moonglade", "The Sha'tar"},
{"Ravenholdt", "Scarshield Legion", "Sporeggar", "The Venture Co"},
{"Sanguino", "Shen'dralar", "Uldum", "Zul'jin"},
{"Skullcrusher", "Xavius"},
{"Thunderhorn", "Wildhammer"},
},
}
function TSMAPI:GetConnectedRealms()
local region = strupper(strsub(GetCVar("realmList"), 1, 2))
if not CONNECTED_REALMS[region] then return end
local currentRealm = GetRealmName()
for _, realms in ipairs(CONNECTED_REALMS[region]) do
for i, realm in ipairs(realms) do
if realm == currentRealm then
local result = CopyTable(realms)
tremove(result, i)
return result
end
end
end
end

717
TradeSkillMaster/Data/Conversions.lua

@ -0,0 +1,717 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local conversions = {
-- Epic WotLK gems
["item:36919:0:0:0:0:0:0"] = { -- Cardinal Ruby
["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:36922:0:0:0:0:0:0"] = { -- King's Amber
["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:36925:0:0:0:0:0:0"] = { -- Majestic Zircon
["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:36928:0:0:0:0:0:0"] = { -- Dreadstone
["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:36931:0:0:0:0:0:0"] = { -- Ametrine
["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:36934:0:0:0:0:0:0"] = { -- Eye of Zul
["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
-- common pigments (inks)
["item:39151:0:0:0:0:0:0"] = { -- Alabaster Pigment (Ivory / Moonglow Ink)
["item:765:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:2447:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:2449:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:39343:0:0:0:0:0:0"] = { -- Azure Pigment (Ink of the Sea)
["item:39969:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:36904:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:36907:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:36901:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:39970:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:37921:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:36905:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:36906:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:36903:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:61979:0:0:0:0:0:0"] = { -- Ashen Pigment (Blackfallow Ink)
["item:52983:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:52984:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:52985:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:52986:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:52987:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:52988:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:39334:0:0:0:0:0:0"] = { -- Dusky Pigment (Midnight Ink)
["item:785:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:2450:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:2452:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:2453:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:3820:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:39339:0:0:0:0:0:0"] = { -- Emerald Pigment (Jadefire Ink)
["item:3818:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:3821:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:3358:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:3819:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:39338:0:0:0:0:0:0"] = { -- Golden Pigment (Lion's Ink)
["item:3355:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:3369:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:3356:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:3357:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:39342:0:0:0:0:0:0"] = { -- Nether Pigment (Ethereal Ink)
["item:22786:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:22785:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:22789:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:22787:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:22790:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:22793:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:22791:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:22792:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:79251:0:0:0:0:0:0"] = { -- Shadow Pigment (Ink of Dreams)
["item:72237:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:72234:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:79010:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:72235:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:79011:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:89639:0:0:0:0:0:0"] = {rate=.5, source="mill"},
},
["item:39341:0:0:0:0:0:0"] = { -- Silvery Pigment (Shimmering Ink)
["item:13464:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:13463:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:13465:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:13466:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:13467:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
["item:39340:0:0:0:0:0:0"] = { -- Violet Pigment (Celestial Ink)
["item:4625:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:8831:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:8838:0:0:0:0:0:0"] = {rate=.5, source="mill"},
["item:8839:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:8845:0:0:0:0:0:0"] = {rate=.6, source="mill"},
["item:8846:0:0:0:0:0:0"] = {rate=.6, source="mill"},
},
-- rare pigments (inks)
["item:43109:0:0:0:0:0:0"] = { -- Icy Pigment (Snowfall Ink)
["item:39969:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:36904:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:36907:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:36901:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:39970:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:37921:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:36905:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:36906:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:36903:0:0:0:0:0:0"] = {rate=.1, source="mill"},
},
["item:61980:0:0:0:0:0:0"] = { -- Burning Embers (Inferno Ink)
["item:52983:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:52984:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:52985:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:52986:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:52987:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:52988:0:0:0:0:0:0"] = {rate=.1, source="mill"},
},
["item:43104:0:0:0:0:0:0"] = { -- Burnt Pigment (Dawnstar Ink)
["item:3356:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:3357:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:3369:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:3355:0:0:0:0:0:0"] = {rate=.05, source="mill"},
},
["item:43108:0:0:0:0:0:0"] = { -- Ebon Pigment (Darkflame Ink)
["item:22792:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:22790:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:22791:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:22793:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:22786:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:22785:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:22787:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:22789:0:0:0:0:0:0"] = {rate=.05, source="mill"},
},
["item:43105:0:0:0:0:0:0"] = { -- Indigo Pigment (Royal Ink)
["item:3358:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:3819:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:3821:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:3818:0:0:0:0:0:0"] = {rate=.05, source="mill"},
},
["item:79253:0:0:0:0:0:0"] = { -- Misty Pigment (Starlight Ink)
["item:72237:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:72234:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:79010:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:72235:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:79011:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:89639:0:0:0:0:0:0"] = {rate=.05, source="mill"},
},
["item:43106:0:0:0:0:0:0"] = { -- Ruby Pigment (Fiery Ink)
["item:4625:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:8838:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:8831:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:8845:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:8846:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:8839:0:0:0:0:0:0"] = {rate=.1, source="mill"},
},
["item:43107:0:0:0:0:0:0"] = { -- Sapphire Pigment (Ink of the Sky)
["item:13463:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:13464:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:13465:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:13466:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:13467:0:0:0:0:0:0"] = {rate=.1, source="mill"},
},
["item:43103:0:0:0:0:0:0"] = { -- Verdant Pigment (Hunter's Ink)
["item:2453:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:3820:0:0:0:0:0:0"] = {rate=.1, source="mill"},
["item:2450:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:785:0:0:0:0:0:0"] = {rate=.05, source="mill"},
["item:2452:0:0:0:0:0:0"] = {rate=.05, source="mill"},
},
--Vanilla Gems
["item:774:0:0:0:0:0:0"] = { -- malachite
["item:2770:0:0:0:0:0:0"] = {rate=.5, source="prospect"},
},
["item:818:0:0:0:0:0:0"] = { -- Tigerseye
["item:2770:0:0:0:0:0:0"] = {rate=.5, source="prospect"},
},
["item:1210:0:0:0:0:0:0"] = { -- Shadowgem
["item:2771:0:0:0:0:0:0"] = {rate=.4, source="prospect"},
["item:2770:0:0:0:0:0:0"] = {rate=.1, source="prospect"},
},
["item:1206:0:0:0:0:0:0"] = { -- Moss Agate
["item:2771:0:0:0:0:0:0"] = {rate=.3, source="prospect"},
},
["item:1705:0:0:0:0:0:0"] = { -- Lesser moonstone
["item:2771:0:0:0:0:0:0"] = {rate=.4, source="prospect"},
["item:2772:0:0:0:0:0:0"] = { rate=.3, source="prospect"},
},
["item:1529:0:0:0:0:0:0"] = { -- Jade
["item:2772:0:0:0:0:0:0"] = {rate=.4, source="prospect"},
["item:2771:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:3864:0:0:0:0:0:0"] = { -- Citrine
["item:2772:0:0:0:0:0:0"] = {rate=.4, source="prospect"}, -- iron
["item:3858:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, -- mith
["item:2771:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, -- tin
},
["item:7909:0:0:0:0:0:0"] = { -- Aquamarine
["item:3858:0:0:0:0:0:0"] = {rate=.3, source="prospect"},
["item:2772:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
["item:2771:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:7910:0:0:0:0:0:0"] = { -- Star Ruby
["item:3858:0:0:0:0:0:0"] = {rate=.4, source="prospect"},
["item:10620:0:0:0:0:0:0"] = {rate=.1, source="prospect"},
["item:2772:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
},
["item:12361:0:0:0:0:0:0"] = { -- Blue Sapphire
["item:10620:0:0:0:0:0:0"] = {rate=.3, source="prospect"},
["item:3858:0:0:0:0:0:0"] = {rate=.03, source="prospect"},
},
["item:12799:0:0:0:0:0:0"] = { -- Large Opal
["item:10620:0:0:0:0:0:0"] = {rate =.3, source="prospect"}, -- thorium
["item:3858:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, -- Mith
},
["item:12800:0:0:0:0:0:0"] = { -- Azerothian Diamond
["item:10620:0:0:0:0:0:0"] = {rate=.3, source="prospect"},
["item:3858:0:0:0:0:0:0"] = {rate=.02, source="prospect"},
},
["item:12364:0:0:0:0:0:0"] = { -- Huge Emerald
["item:10620:0:0:0:0:0:0"] = {rate=.3, source="prospect"},
["item:3858:0:0:0:0:0:0"] = {rate=.02, source="prospect"},
},
-- uncommon gems
["item:23117:0:0:0:0:0:0"] = { -- Azure Moonstone
["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
},
["item:23077:0:0:0:0:0:0"] = { -- Blood Garnet
["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
},
["item:23079:0:0:0:0:0:0"] = { -- Deep Peridot
["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
},
["item:21929:0:0:0:0:0:0"] = { -- Flame Spessarite
["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
},
["item:23112:0:0:0:0:0:0"] = { -- Golden Draenite
["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
},
["item:23107:0:0:0:0:0:0"] = { -- Shadow Draenite
["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
},
["item:36917:0:0:0:0:0:0"] = { -- Bloodstone
["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
},
["item:36923:0:0:0:0:0:0"] = { -- Chalcedony
["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
},
["item:36932:0:0:0:0:0:0"] = { -- Dark Jade
["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
},
["item:36929:0:0:0:0:0:0"] = { -- Huge Citrine
["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
},
["item:36926:0:0:0:0:0:0"] = { -- Shadow Crystal
["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
},
["item:36920:0:0:0:0:0:0"] = { -- Sun Crystal
["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
},
-- ["item:52182:0:0:0:0:0:0"] = { -- Jasper
-- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:52180:0:0:0:0:0:0"] = { -- Nightstone
-- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:52178:0:0:0:0:0:0"] = { -- Zephyrite
-- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:52179:0:0:0:0:0:0"] = { -- Alicite
-- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:52177:0:0:0:0:0:0"] = { -- Carnelian
-- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:52181:0:0:0:0:0:0"] = { -- Hessonite
-- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:76130:0:0:0:0:0:0"] = { -- Tiger Opal
-- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:76133:0:0:0:0:0:0"] = { -- Lapis Lazuli
-- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:76134:0:0:0:0:0:0"] = { -- Sunstone
-- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:76135:0:0:0:0:0:0"] = { -- Roguestone
-- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:76136:0:0:0:0:0:0"] = { -- Pandarian Garnet
-- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
-- ["item:76137:0:0:0:0:0:0"] = { -- Alexandrite
-- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"},
-- },
--Rare Gems
["item:23440:0:0:0:0:0:0"] = { -- Dawnstone
["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:23436:0:0:0:0:0:0"] = { -- Living Ruby
["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:23441:0:0:0:0:0:0"] = { -- Nightseye
["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:23439:0:0:0:0:0:0"] = { -- Noble Topaz
["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:23438:0:0:0:0:0:0"] = { -- Star of Elune
["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:23437:0:0:0:0:0:0"] = { -- Talasite
["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:36921:0:0:0:0:0:0"] = { -- Autumn's Glow
["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:36933:0:0:0:0:0:0"] = { -- Forest Emerald
["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:36930:0:0:0:0:0:0"] = { -- Monarch Topaz
["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:36918:0:0:0:0:0:0"] = { -- Scarlet Ruby
["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:36924:0:0:0:0:0:0"] = { -- Sky Sapphire
["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
["item:36927:0:0:0:0:0:0"] = { -- Twilight Opal
["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"},
["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
},
-- ["item:52192:0:0:0:0:0:0"] = { -- Dream Emerald
-- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- },
-- ["item:52193:0:0:0:0:0:0"] = { -- Ember Topaz
-- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- },
-- ["item:52190:0:0:0:0:0:0"] = { -- Inferno Ruby
-- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- },
-- ["item:52195:0:0:0:0:0:0"] = { -- Amberjewel
-- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- },
-- ["item:52194:0:0:0:0:0:0"] = { -- Demonseye
-- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- },
-- ["item:52191:0:0:0:0:0:0"] = { -- Ocean Sapphire
-- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"},
-- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"},
-- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- },
-- ["item:76131:0:0:0:0:0:0"] = { -- Primordial Ruby
-- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- },
-- ["item:76138:0:0:0:0:0:0"] = { -- River's Heart
-- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- },
-- ["item:76139:0:0:0:0:0:0"] = { -- Wild Jade
-- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- },
-- ["item:76140:0:0:0:0:0:0"] = { -- Vermillion Onyx
-- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- },
-- ["item:76141:0:0:0:0:0:0"] = { -- Imperial Amethyst
-- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- },
-- ["item:76142:0:0:0:0:0:0"] = { -- Sun's Radiance
-- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"},
-- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"},
-- },
--transformations
-- ["item:52719:0:0:0:0:0:0"] = { -- Greater Celestial Essence
-- ["item:52718:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
-- },
["item:52718:0:0:0:0:0:0"] = { -- Lesser Celestial Essence
["item:52719:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:34055:0:0:0:0:0:0"] = { -- Greater Cosmic Essence
["item:34056:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:34056:0:0:0:0:0:0"] = { -- Lesser Cosmic Essence
["item:34055:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:22446:0:0:0:0:0:0"] = { -- Greater Planar Essence
["item:22447:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:22447:0:0:0:0:0:0"] = { -- Lesser Planar Essence
["item:22446:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:16203:0:0:0:0:0:0"] = { -- Greater Eternal Essence
["item:16202:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:16202:0:0:0:0:0:0"] = { -- Lesser Eternal Essence
["item:16203:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:11175:0:0:0:0:0:0"] = { -- Greater Nether Essence
["item:11174:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:11174:0:0:0:0:0:0"] = { -- Lesser Nether Essence
["item:11175:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:11135:0:0:0:0:0:0"] = { -- Greater Mystic Essence
["item:11134:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:11134:0:0:0:0:0:0"] = { -- Lesser Mystic Essence
["item:11135:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:11082:0:0:0:0:0:0"] = { -- Greater Astral Essence
["item:10998:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:10998:0:0:0:0:0:0"] = { -- Lesser Astral Essence
["item:11082:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:10939:0:0:0:0:0:0"] = { -- Greater Magic Essence
["item:10938:0:0:0:0:0:0"] = {rate=3, source="transform"},
},
["item:10938:0:0:0:0:0:0"] = { -- Lesser Magic Essence
["item:10939:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:52721:0:0:0:0:0:0"] = { -- Heavenly Shard
["item:52720:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
["item:34052:0:0:0:0:0:0"] = { -- Dream Shard
["item:34053:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
},
-- ["item:74247:0:0:0:0:0:0"] = { -- Ethereal Shard
-- ["item:74252:0:0:0:0:0:0"] = {rate=1/3, source="transform"},
-- },
["item:22578:0:0:0:0:0:0"] = { -- Mote of Water
["item:21885:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:21885:0:0:0:0:0:0"] = { -- Primal Water
["item:22578:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:22577:0:0:0:0:0:0"] = { -- Mote of Shadow
["item:22456:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:22456:0:0:0:0:0:0"] = { -- Primal Shadow
["item:22577:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:22576:0:0:0:0:0:0"] = { -- Mote of Mana
["item:22457:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:22457:0:0:0:0:0:0"] = { -- Primal Mana
["item:22576:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:22575:0:0:0:0:0:0"] = { -- Mote of Life
["item:21886:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:21886:0:0:0:0:0:0"] = { -- Primal Life
["item:22575:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:22573:0:0:0:0:0:0"] = { -- Mote of Earth
["item:22452:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:22452:0:0:0:0:0:0"] = { -- Primal Earth
["item:22573:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:22574:0:0:0:0:0:0"] = { -- Mote of Air
["item:21884:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:21884:0:0:0:0:0:0"] = { -- Primal Air
["item:22574:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:37700:0:0:0:0:0:0"] = { -- Crystallized Air
["item:35623:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:35623:0:0:0:0:0:0"] = { -- Eternal Air
["item:37700:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:37701:0:0:0:0:0:0"] = { -- Crystallized Earth
["item:35624:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:35624:0:0:0:0:0:0"] = { -- Eternal Earth
["item:37701:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:37702:0:0:0:0:0:0"] = { -- Crystallized Fire
["item:36860:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:36860:0:0:0:0:0:0"] = { -- Eternal Fire
["item:37702:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:37703:0:0:0:0:0:0"] = { -- Crystallized Shadow
["item:35627:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:35627:0:0:0:0:0:0"] = { -- Eternal Shadow
["item:37703:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:37704:0:0:0:0:0:0"] = { -- Crystallized Life
["item:35625:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:35625:0:0:0:0:0:0"] = { -- Eternal Life
["item:37704:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
["item:37705:0:0:0:0:0:0"] = { -- Crystallized Water
["item:35622:0:0:0:0:0:0"] = {rate=10, source="transform"},
},
["item:35622:0:0:0:0:0:0"] = { -- Eternal Water
["item:37705:0:0:0:0:0:0"] = {rate=1/10, source="transform"},
},
--vendor trades
["item:37101:0:0:0:0:0:0"] = { -- Ivory Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:39469:0:0:0:0:0:0"] = { -- Moonglow Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:39774:0:0:0:0:0:0"] = { -- Midnight Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43116:0:0:0:0:0:0"] = { -- Lion's Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43118:0:0:0:0:0:0"] = { -- Jadefire Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43120:0:0:0:0:0:0"] = { -- Celestial Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43122:0:0:0:0:0:0"] = { -- Shimmering Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43124:0:0:0:0:0:0"] = { -- Ethereal Ink
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43126:0:0:0:0:0:0"] = { -- Ink of the Sea
["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
},
["item:43127:0:0:0:0:0:0"] = { -- Snowfall Ink
["item:79254:0:0:0:0:0:0"] = {rate=1/10, source="vendortrade"},
},
-- ["item:61978:0:0:0:0:0:0"] = { -- Blackfallow Ink
-- ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"},
-- },
-- ["item:61981:0:0:0:0:0:0"] = { -- Inferno Ink
-- ["item:79254:0:0:0:0:0:0"] = {rate=1/10, source="vendortrade"},
-- },
-- ["item:79255:0:0:0:0:0:0"] = { -- Starlight Ink
-- ["item:79254:0:0:0:0:0:0"] = {rate=1/10, source="vendortrade"},
-- },
}
TSMAPI.Conversions = conversions
local inks = {
-- uncommon inks
["item:37101:0:0:0:0:0:0"] = {pigment="item:39151:0:0:0:0:0:0", pigmentPerInk=1}, -- Ivory Ink
["item:39469:0:0:0:0:0:0"] = {pigment="item:39151:0:0:0:0:0:0", pigmentPerInk=2}, -- Moonglow Ink
["item:39774:0:0:0:0:0:0"] = {pigment="item:39334:0:0:0:0:0:0", pigmentPerInk=2}, -- Midnight Ink
["item:43116:0:0:0:0:0:0"] = {pigment="item:39338:0:0:0:0:0:0", pigmentPerInk=2}, -- Lion's Ink
["item:43118:0:0:0:0:0:0"] = {pigment="item:39339:0:0:0:0:0:0", pigmentPerInk=2}, -- Jadefire Ink
["item:43120:0:0:0:0:0:0"] = {pigment="item:39340:0:0:0:0:0:0", pigmentPerInk=2}, -- Celestial Ink
["item:43122:0:0:0:0:0:0"] = {pigment="item:39341:0:0:0:0:0:0", pigmentPerInk=2}, -- Shimmering Ink
["item:43124:0:0:0:0:0:0"] = {pigment="item:39342:0:0:0:0:0:0", pigmentPerInk=2}, -- Ethereal Ink
["item:43126:0:0:0:0:0:0"] = {pigment="item:39343:0:0:0:0:0:0", pigmentPerInk=2}, -- Ink of the Sea
-- ["item:61978:0:0:0:0:0:0"] = {pigment="item:61979:0:0:0:0:0:0", pigmentPerInk=2}, -- Blackfallow Ink
-- ["item:79254:0:0:0:0:0:0"] = {pigment="item:79251:0:0:0:0:0:0", pigmentPerInk=2}, -- Ink of Dreams
-- rare inks
["item:43115:0:0:0:0:0:0"] = {pigment="item:43103:0:0:0:0:0:0", pigmentPerInk=1}, -- Hunter's Ink
["item:43117:0:0:0:0:0:0"] = {pigment="item:43104:0:0:0:0:0:0", pigmentPerInk=1}, -- Dawnstar Ink
["item:43119:0:0:0:0:0:0"] = {pigment="item:43105:0:0:0:0:0:0", pigmentPerInk=1}, -- Royal Ink
["item:43121:0:0:0:0:0:0"] = {pigment="item:43106:0:0:0:0:0:0", pigmentPerInk=1}, -- Fiery Ink
["item:43123:0:0:0:0:0:0"] = {pigment="item:43107:0:0:0:0:0:0", pigmentPerInk=1}, -- Ink of the Sky
["item:43125:0:0:0:0:0:0"] = {pigment="item:43108:0:0:0:0:0:0", pigmentPerInk=1}, -- Darkflame Ink
["item:43127:0:0:0:0:0:0"] = {pigment="item:43109:0:0:0:0:0:0", pigmentPerInk=2}, -- Snowfall Ink
-- ["item:61981:0:0:0:0:0:0"] = {pigment="item:61980:0:0:0:0:0:0", pigmentPerInk=2}, -- Inferno Ink
-- ["item:79255:0:0:0:0:0:0"] = {pigment="item:79253:0:0:0:0:0:0", pigmentPerInk=2}, -- Starlight Ink
}
TSMAPI.InkConversions = inks
-- returns the conversion info for a given target item
function TSMAPI:GetItemConversions(itemString)
if not itemString or not conversions[itemString] then return end
return CopyTable(conversions[itemString])
end
function TSMAPI:GetConvertCost(targetItem, priceSource)
local conversions = TSMAPI:GetItemConversions(targetItem)
if not conversions then return end
local prices = {}
for itemString, info in pairs(conversions) do
local price = TSMAPI:GetItemValue(itemString, priceSource)
if price then
tinsert(prices, price/info.rate)
end
end
if #prices == 0 then return end
return min(unpack(prices))
end
function TSMAPI:GetConversionTargetItems(source)
local result = {}
for itemString, items in pairs(conversions) do
for _, info in pairs(items) do
if info.source == source then
tinsert(result, itemString)
break
end
end
end
return result
end

1636
TradeSkillMaster/Data/Disenchanting.lua

File diff suppressed because it is too large Load Diff

32
TradeSkillMaster/Data/ItemData.lua

@ -0,0 +1,32 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- random lookup tables and other functions that don't have a home go in here
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
TSMAPI.EquipLocLookup = {
[INVTYPE_HEAD]=1, [INVTYPE_NECK]=2, [INVTYPE_SHOULDER]=3, [INVTYPE_BODY]=4, [INVTYPE_CHEST]=5,
[INVTYPE_WAIST]=6, [INVTYPE_LEGS]=7, [INVTYPE_FEET]=8, [INVTYPE_WRIST]=9, [INVTYPE_HAND]=10,
[INVTYPE_FINGER]=11, [INVTYPE_TRINKET]=12, [INVTYPE_CLOAK]=13, [INVTYPE_HOLDABLE]=14,
[INVTYPE_WEAPONMAINHAND]=15, [INVTYPE_ROBE]=16, [INVTYPE_TABARD]=17, [INVTYPE_BAG]=18,
[INVTYPE_2HWEAPON]=19, [INVTYPE_RANGED]=20, [INVTYPE_SHIELD]=21, [INVTYPE_WEAPON]=22
}
TSMAPI.SOULBOUND_MATS = {
-- ["item:79731:0:0:0:0:0:0"] = true, -- Scroll of Wisdom
-- ["item:76061:0:0:0:0:0:0"] = true, -- Spirit of Harmony
-- ["item:82447:0:0:0:0:0:0"] = true, -- Imperial Silk
-- ["item:54440:0:0:0:0:0:0"] = true, -- Dreamcloth
-- ["item:94111:0:0:0:0:0:0"] = true, -- Lightning Steel Ingot
-- ["item:94113:0:0:0:0:0:0"] = true, -- Jard's Peculiar Energy Source
-- ["item:98717:0:0:0:0:0:0"] = true, -- Balanced Trillium Ingot
-- ["item:98619:0:0:0:0:0:0"] = true, -- Celestial Cloth
-- ["item:98617:0:0:0:0:0:0"] = true, -- Hardened Magnificent Hide
}

104
TradeSkillMaster/Data/Vendor.lua

@ -0,0 +1,104 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local Vendor = TSM:NewModule("Vendor", "AceEvent-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
function Vendor:OnEnable()
Vendor:RegisterEvent("MERCHANT_SHOW", "ScanMerchant")
end
local vendorItems = {
["item:2320:0:0:0:0:0:0"] = 10,
["item:2321:0:0:0:0:0:0"] = 10,
["item:2324:0:0:0:0:0:0"] = 25,
["item:2325:0:0:0:0:0:0"] = 1000,
["item:2604:0:0:0:0:0:0"] = 50,
["item:2605:0:0:0:0:0:0"] = 10,
["item:2678:0:0:0:0:0:0"] = 10,
["item:2880:0:0:0:0:0:0"] = 100,
["item:3371:0:0:0:0:0:0"] = 100,
["item:3466:0:0:0:0:0:0"] = 2000,
["item:4289:0:0:0:0:0:0"] = 50,
["item:4291:0:0:0:0:0:0"] = 500,
["item:4340:0:0:0:0:0:0"] = 350,
["item:4341:0:0:0:0:0:0"] = 500,
["item:4342:0:0:0:0:0:0"] = 2500,
["item:4399:0:0:0:0:0:0"] = 200,
["item:4400:0:0:0:0:0:0"] = 2000,
["item:4470:0:0:0:0:0:0"] = 38,
["item:6260:0:0:0:0:0:0"] = 50,
["item:6261:0:0:0:0:0:0"] = 100,
["item:8343:0:0:0:0:0:0"] = 2000,
["item:10290:0:0:0:0:0:0"] = 2500,
["item:10647:0:0:0:0:0:0"] = 2000,
["item:10648:0:0:0:0:0:0"] = 100,
["item:11291:0:0:0:0:0:0"] = 4500,
["item:14341:0:0:0:0:0:0"] = 5000,
["item:17020:0:0:0:0:0:0"] = 1000,
["item:17194:0:0:0:0:0:0"] = 10,
["item:17196:0:0:0:0:0:0"] = 50,
["item:30817:0:0:0:0:0:0"] = 25,
["item:34412:0:0:0:0:0:0"] = 1000,
["item:35949:0:0:0:0:0:0"] = 8500,
["item:38426:0:0:0:0:0:0"] = 30000,
["item:38682:0:0:0:0:0:0"] = 1000,
["item:39354:0:0:0:0:0:0"] = 15,
["item:39501:0:0:0:0:0:0"] = 1200,
["item:39502:0:0:0:0:0:0"] = 5000,
["item:39684:0:0:0:0:0:0"] = 9000,
["item:40533:0:0:0:0:0:0"] = 50000,
["item:44835:0:0:0:0:0:0"] = 10,
["item:44853:0:0:0:0:0:0"] = 25,
-- ["item:52188:0:0:0:0:0:0"] = 15000,
-- ["item:58274:0:0:0:0:0:0"] = 11000,
-- ["item:58278:0:0:0:0:0:0"] = 16000,
-- ["item:62323:0:0:0:0:0:0"] = 60000,
-- ["item:62786:0:0:0:0:0:0"] = 1000,
-- ["item:62787:0:0:0:0:0:0"] = 1000,
-- ["item:62788:0:0:0:0:0:0"] = 1000,
-- ["item:67319:0:0:0:0:0:0"] = 328990,
-- ["item:67335:0:0:0:0:0:0"] = 445561,
-- ["item:67348:0:0:0:0:0:0"] = 394755,
-- ["item:68047:0:0:0:0:0:0"] = 170437,
-- ["item:74659:0:0:0:0:0:0"] = 30000,
-- ["item:74660:0:0:0:0:0:0"] = 15000,
-- ["item:74832:0:0:0:0:0:0"] = 12000,
-- ["item:74845:0:0:0:0:0:0"] = 35000,
-- ["item:74851:0:0:0:0:0:0"] = 14000,
-- ["item:74852:0:0:0:0:0:0"] = 16000,
-- ["item:74854:0:0:0:0:0:0"] = 7000,
-- ["item:79740:0:0:0:0:0:0"] = 23,
-- ["item:83092:0:0:0:0:0:0"] = 20000.0000,
-- ["item:85583:0:0:0:0:0:0"] = 12000,
-- ["item:85584:0:0:0:0:0:0"] = 17000,
-- ["item:85585:0:0:0:0:0:0"] = 27000,
}
-- returns the vendor cost for a given target item
function TSMAPI:GetVendorCost(itemString)
return itemString and TSM.db.global.vendorItems[itemString] or vendorItems[itemString]
end
function Vendor:ScanMerchant(first)
for i=1, GetMerchantNumItems() do
local itemString = TSMAPI:GetItemString(GetMerchantItemLink(i))
if itemString then
local _, _, price, _, numAvailable, _, extendedCost = GetMerchantItemInfo(i)
if price > 0 and not extendedCost and numAvailable == -1 then
TSM.db.global.vendorItems[itemString] = price
else
TSM.db.global.vendorItems[itemString] = nil
end
end
end
if first then
TSMAPI:CreateTimeDelay("scanMerchantDelay", 1, function() Vendor:ScanMerchant() end)
end
end

346
TradeSkillMaster/GUI/BankUI.lua

@ -0,0 +1,346 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains function for the bank/gbank frame
-- loads the localization table --
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
-- load the parent file (TSM) into a local variable and register this file as a module
local TSM = select(2, ...)
local BankUI = TSM:NewModule("BankUI", "AceEvent-3.0")
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI librarie
local ui
local bankFrame, bankType
local bFrame = nil
local container = nil
local registeredModules = {}
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.BankUI_private")
private.bankUiButtons = {}
function BankUI:OnEnable()
BankUI:RegisterEvent("GUILDBANKFRAME_OPENED", function(event)
bankType = "guild"
TSMAPI:CreateTimeDelay(0.1, function()
bankFrame = BankUI:getBankFrame("guildbank")
if TSM.db.profile.isBankui then
if #private.bankUiButtons > 0 then
if ui then
BankUI:resetPoints(ui)
ui:Show()
if TSM.db.global.bankUITab then
for index, info in ipairs(private.bankUiButtons) do
if info.moduleName == TSM.db.global.bankUITab then
ui.buttons[index]:Click()
break
end
end
else
ui.buttons[1]:Click()
end
return
end
ui = BankUI:getFrame(bankFrame)
end
end
end)
end)
BankUI:RegisterEvent("BANKFRAME_OPENED", function(event)
bankType = "bank"
TSMAPI:CreateTimeDelay(0.1, function()
bankFrame = BankUI:getBankFrame("bank")
if TSM.db.profile.isBankui then
if #private.bankUiButtons > 0 then
if ui then
BankUI:resetPoints(ui)
ui:Show()
if TSM.db.global.bankUITab then
for index, info in ipairs(private.bankUiButtons) do
if info.moduleName == TSM.db.global.bankUITab then
ui.buttons[index]:Click()
break
end
end
else
ui.buttons[1]:Click()
end
return
end
ui = BankUI:getFrame(bankFrame)
end
end
end)
end)
BankUI:RegisterEvent("GUILDBANKFRAME_CLOSED", function(event, addon)
if ui then ui:Hide() end
bankType = nil
end)
BankUI:RegisterEvent("BANKFRAME_CLOSED", function(event)
if ui then ui:Hide() end
bankType = nil
end)
end
local function createCloseButton(text, parent, func)
local btn = TSMAPI.GUI:CreateButton(bFrame, 18, "Button")
btn:SetText(text)
btn:SetHeight(20)
btn:SetWidth(20)
return btn
end
function TSM:RegisterBankUiButton(moduleName, callback)
if registeredModules[moduleName] then return end
registeredModules[moduleName] = true
local info = {}
info.moduleName = moduleName
info.callback = callback
tinsert(private.bankUiButtons, info)
sort(private.bankUiButtons, function(a, b)
if a.moduleName == "Warehousing" then
return true
elseif b.moduleName == "Warehousing" then
return false
else
return a.moduleName < b.moduleName
end
end)
end
function BankUI:getBankFrame(bank)
if BagnonFrameguildbank and BagnonFrameguildbank:IsVisible() then
return BagnonFrameguildbank
elseif BagnonFramebank and BagnonFramebank:IsVisible() then
return BagnonFramebank
elseif GuildBankFrame and GuildBankFrame:IsVisible() then
return GuildBankFrame
elseif BankFrame and BankFrame:IsVisible() then
return BankFrame
elseif (famBankFrame and famBankFrame:IsVisible()) then
return famBankFrame
elseif (ARKINV_Frame4 and ARKINV_Frame4:IsVisible()) then
return ARKINV_Frame4
elseif (ARKINV_Frame3 and ARKINV_Frame3:IsVisible()) then
return ARKINV_Frame3
elseif (OneBankFrame and OneBankFrame:IsVisible()) then
return OneBankFrame
elseif (TukuiBank and TukuiBank:IsShown()) then
return TukuiBank
elseif (ElvUI_BankContainerFrame and ElvUI_BankContainerFrame:IsVisible()) then
return ElvUI_BankContainerFrame
elseif (LUIBank and LUIBank:IsVisible()) then
return LUIBank
elseif (AdiBagsContainer1 and AdiBagsContainer1.isBank and AdiBagsContainer1:IsVisible()) then
return AdiBagsContainer1
elseif (AdiBagsContainer2 and AdiBagsContainer2.isBank and AdiBagsContainer2:IsVisible()) then
return AdiBagsContainer2
elseif (BagsFrameBank and BagsFrameBank:IsVisible()) then
return BagsFrameBank
elseif AspUIBank and AspUIBank:IsVisible() then
return AspUIBank
elseif NivayacBniv_Bank and NivayacBniv_Bank:IsVisible() then
return NivayacBniv_Bank
elseif DufUIBank and DufUIBank:IsVisible() then
return DufUIBank
end
return nil
end
function BankUI:getFrame(frameType)
bFrame = CreateFrame("Frame", nil, UIParent)
bFrame:Hide()
--size--
bFrame:SetWidth(275)
bFrame:SetHeight(470)
bFrame:SetPoint("CENTER", UIParent)
--for moving--
bFrame:SetScript("OnMouseDown", bFrame.StartMoving)
bFrame:SetScript("OnMouseUp", function(...) bFrame.StopMovingOrSizing(...)
if bankType == "guild" then
TSM.db.factionrealm.bankUIGBankFramePosition = { bFrame:GetLeft(), bFrame:GetBottom() }
else
TSM.db.factionrealm.bankUIBankFramePosition = { bFrame:GetLeft(), bFrame:GetBottom() }
end
end)
bFrame:SetMovable(true)
bFrame:EnableMouse(true)
bFrame:SetPoint("CENTER", UIParent)
local function OnFrameShow(self)
self:SetFrameLevel(0)
self:ClearAllPoints()
if bankType == "guild" then
self:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIGBankFramePosition))
else
self:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIBankFramePosition))
end
end
TSMAPI.Design:SetFrameBackdropColor(bFrame)
bFrame:SetScript("OnShow", OnFrameShow)
bFrame:Show()
local title = TSMAPI.GUI:CreateLabel(bFrame)
title:SetPoint("TOPLEFT", 40, -3)
title:SetPoint("BOTTOMRIGHT", bFrame, "TOPRIGHT", -5, -23)
title:SetJustifyH("CENTER")
title:SetJustifyV("CENTER")
title:SetText("TradeSkillMaster - " .. TSM._version)
TSMAPI.Design:SetTitleTextColor(title)
local title2 = TSMAPI.GUI:CreateLabel(bFrame)
title2:SetPoint("TOPLEFT", title, "BOTTOMLEFT")
title2:SetPoint("TOPRIGHT", title, "BOTTOMRIGHT")
title2:SetJustifyH("CENTER")
title2:SetJustifyV("CENTER")
title2:SetText(L["BankUI"])
TSMAPI.Design:SetTitleTextColor(title2)
bFrame.btnClose = createCloseButton("X", bFrame, nil)
bFrame.btnClose:SetPoint("TOPRIGHT", bFrame, "TOPRIGHT")
bFrame.btnClose:SetScript("OnClick", function(self)
if bFrame then bFrame:Hide() end
TSM.db.profile.isBankui = false
TSM:Print(L["You have closed the bankui. Use '/tsm bankui' to view again."])
end)
-- module buttons
bFrame.buttons = {}
local iconFrame = CreateFrame("Frame", nil, bFrame)
iconFrame:SetPoint("CENTER", bFrame, "TOPLEFT", 25, -25)
iconFrame:SetHeight(80)
iconFrame:SetWidth(80)
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
icon:SetAllPoints()
icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Big")
local ag = iconFrame:CreateAnimationGroup()
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(1)
spin:SetDuration(2)
spin:SetDegrees(90)
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(2)
spin:SetDuration(4)
spin:SetDegrees(-180)
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(3)
spin:SetDuration(2)
spin:SetDegrees(90)
ag:SetLooping("REPEAT")
iconFrame:SetScript("OnEnter", function() ag:Play() end)
iconFrame:SetScript("OnLeave", function() ag:Stop() end)
container = CreateFrame("Frame", nil, bFrame)
container:SetPoint("TOPLEFT", 5, -60)
container:SetPoint("BOTTOMRIGHT", -5, 5)
TSMAPI.Design:SetFrameColor(container)
for _, info in ipairs(private.bankUiButtons) do
info.bankTab = info.callback(container)
private:CreateBankButton(info.moduleName)
end
if TSM.db.global.bankUITab then
for index, info in ipairs(private.bankUiButtons) do
if info.moduleName == TSM.db.global.bankUITab then
bFrame.buttons[index]:Click()
break
end
end
else
bFrame.buttons[1]:Click()
end
return bFrame
end
function BankUI:resetPoints(container)
if bankType == "guild" then
container:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIGBankFramePosition))
else
container:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIBankFramePosition))
end
end
function private:CreateBankButton(module)
local buttonIndex
for record, info in ipairs(private.bankUiButtons) do
if info.moduleName == module then
buttonIndex = record
end
end
local function OnButtonClick(self)
for _, info in ipairs(private.bankUiButtons) do
info.bankTab:Hide()
end
for index, button in ipairs(bFrame.buttons) do
button:UnlockHighlight()
if self == button then
private.bankUiButtons[index].bankTab:Show()
end
end
self:LockHighlight()
end
local button = TSMAPI.GUI:CreateButton(bFrame, 12)
if buttonIndex == 1 then
button:SetPoint("TOPLEFT", 70, -40)
else
button:SetPoint("TOPLEFT", bFrame.buttons[buttonIndex - 1], "TOPRIGHT", 5, 0)
end
button:SetHeight(20)
button:SetWidth(70)
button:SetText(module)
button:SetScript("OnClick", OnButtonClick)
tinsert(bFrame.buttons, button)
end
function TSM:toggleBankUI()
if TSM:areBanksVisible() then
if ui then
if ui:IsShown() then
ui:Hide()
else
ui:Show()
end
else
ui = BankUI:getFrame(bankFrame)
TSM.db.profile.isBankui = true
end
else
TSM:Print(L["There are no visible banks."])
end
end
function TSM:getBankTabs()
local tabs = {}
for record, info in ipairs(private.bankUiButtons) do
tabs[info.moduleName] = info.moduleName
end
return tabs
end
function TSM:ResetBankUIFramePosition()
TSM.db.factionrealm.bankUIGBankFramePosition = { 100, 300 }
TSM.db.factionrealm.bankUIBankFramePosition = { 100, 300 }
if ui then
ui:Hide()
ui:Show()
end
end

433
TradeSkillMaster/GUI/BuildPage.lua

@ -0,0 +1,433 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local lib = TSMAPI
local customPriceFrame
--[[-----------------------------------------------------------------------------
TSMAPI:BuildPage() Support Functions
-------------------------------------------------------------------------------]]
local function CreateCustomPriceFrame()
local frame = CreateFrame("Frame", nil, TSMMainFrame1)
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:Hide()
frame:SetPoint("TOPLEFT", TSMMainFrame1, "TOPRIGHT", 2, 0)
frame:SetWidth(300)
frame:SetHeight(400)
local container = AceGUI:Create("TSMScrollFrame")
container:SetLayout("Flow")
container.frame:SetParent(frame)
container.frame:SetPoint("TOPLEFT", 5, -5)
container.frame:SetPoint("BOTTOMRIGHT", -5, 5)
local page = {
{
type = "Label",
relativeWidth = 1,
text = L["Below are various ways you can set the value of the current editbox. Any combination of these methods is also supported."],
},
{
type = "HeadingLine",
relativeWidth = 1,
},
{
type = "Label",
text = TSMAPI.Design:GetInlineColor("category")..L["Fixed Gold Value"].."|r",
relativeWidth = 1,
},
{
type = "Label",
text = L["A simple, fixed gold amount."],
relativeWidth = 1,
},
{
type = "HeadingLine",
relativeWidth = 1,
},
{
type = "Label",
text = TSMAPI.Design:GetInlineColor("category")..L["Percent of Price Source"].."|r",
relativeWidth = 1,
},
{
type = "Label",
text = L["Type '/tsm sources' to print out all available price sources."],
relativeWidth = 1,
},
{
type = "HeadingLine",
relativeWidth = 1,
},
{
type = "Label",
text = TSMAPI.Design:GetInlineColor("category")..L["More Advanced Methods"].."|r",
relativeWidth = 1,
},
{
type = "Label",
text = format("See %s for more info.", TSMAPI.Design:GetInlineColor("link").."http://bit.ly/TSMCP|r"),
relativeWidth = 1,
},
{
type = "HeadingLine",
relativeWidth = 1,
},
{
type = "Label",
text = TSMAPI.Design:GetInlineColor("category")..L["Examples"].."|r",
relativeWidth = 1,
},
{
type = "Label",
text = "20g50s",
relativeWidth = 1,
},
{
type = "Label",
text = "120% crafting",
relativeWidth = 1,
},
{
type = "Label",
text = "100% vendor + 5g",
relativeWidth = 1,
},
{
type = "Label",
text = "max(150% dbmarket, 1.2 * crafting)",
relativeWidth = 1,
},
{
type = "Label",
text = "max(vendor, 120% crafting)",
relativeWidth = 1,
},
}
TSMAPI:BuildPage(container, page)
return frame
end
local function FormatCopperCustomPrice(value)
value = gsub(value, TSMAPI:StrEscape(TSM.GOLD_TEXT), "g")
value = gsub(value, TSMAPI:StrEscape(TSM.SILVER_TEXT), "s")
value = gsub(value, TSMAPI:StrEscape(TSM.COPPER_TEXT), "c")
local goldPart = select(3, strfind(value, "([0-9]+g)"))
local silverPart = select(3, strfind(value, "([0-9]+s)"))
local copperPart = select(3, strfind(value, "([0-9]+c)"))
if copperPart then
value = gsub(value, copperPart, gsub(copperPart, "c", TSM.COPPER_TEXT))
end
if silverPart then
value = gsub(value, silverPart, gsub(silverPart, "s", TSM.SILVER_TEXT))
end
if goldPart then
value = gsub(value, goldPart, gsub(goldPart, "g", TSM.GOLD_TEXT))
end
return value
end
local function AddTooltip(widget, text, title)
if not text then return end
widget:SetCallback("OnEnter", function(self)
GameTooltip:SetOwner(self.frame, "ANCHOR_NONE")
GameTooltip:SetPoint("BOTTOM", self.frame, "TOP")
if title then
GameTooltip:SetText(title, 1, .82, 0, 1)
end
if type(text) == "number" then
GameTooltip:SetHyperlink("item:" .. text)
elseif tonumber(text) then
GameTooltip:SetHyperlink("enchant:"..text)
elseif type(text) == "string" and (strfind(text, "item:")) then
TSMAPI:SafeTooltipLink(text)
else
GameTooltip:AddLine(text, 1, 1, 1, 1)
end
GameTooltip:Show()
end)
widget:SetCallback("OnLeave", function()
-- BattlePetTooltip:Hide()
GameTooltip:ClearLines()
GameTooltip:Hide()
end)
end
local function CreateContainer(cType, parent, args)
local container = AceGUI:Create(cType)
if not container then return end
container:SetLayout(args.layout)
if args.title then container:SetTitle(args.title) end
container:SetRelativeWidth(args.relativeWidth or 1)
container:SetFullHeight(args.fullHeight)
parent:AddChild(container)
return container
end
local function CreateWidget(wType, parent, args)
local widget = AceGUI:Create(wType)
if args.settingInfo then
args.value = args.value or args.settingInfo[1][args.settingInfo[2]]
if args.acceptCustom then
if tonumber(args.value) then
args.value = TSMAPI:FormatTextMoney(args.value)
elseif args.value then
args.value = FormatCopperCustomPrice(args.value)
end
end
local oldCallback = args.callback
args.callback = function(...)
local value = select(3, ...)
if type(value) == "string" then value = value:trim() end
if args.multiselect then
local key = value
value = select(4, ...)
args.settingInfo[1][args.settingInfo[2]][key] = value
else
args.settingInfo[1][args.settingInfo[2]] = value
end
if oldCallback then oldCallback(...) end
end
end
if args.text then widget:SetText(args.text) end
if args.label then widget:SetLabel(args.label) end
if args.width then
widget:SetWidth(args.width)
elseif args.relativeWidth then
if args.relativeWidth == 1 then
widget:SetFullWidth(true)
else
widget:SetRelativeWidth(args.relativeWidth)
end
end
if args.height then widget:SetHeight(args.height) end
if widget.SetDisabled then widget:SetDisabled(args.disabled) end
AddTooltip(widget, args.tooltip, args.label)
parent:AddChild(widget)
return widget
end
local Add = {
InlineGroup = function(parent, args)
local container = CreateContainer("TSMInlineGroup", parent, args)
container:HideTitle(not args.title)
container:HideBorder(args.noBorder)
container:SetBackdrop(args.backdrop)
return container
end,
SimpleGroup = function(parent, args)
local container = CreateContainer("TSMSimpleGroup", parent, args)
if args.height then container:SetHeight(args.height) end
return container
end,
ScrollFrame = function(parent, args)
return CreateContainer("TSMScrollFrame", parent, args)
end,
Image = function(parent, args)
local image = CreateWidget("TSMImage", parent, args)
image:SetImage(args.image)
image:SetSizeRatio(args.sizeRatio)
return image
end,
Label = function(parent, args)
local labelWidget = CreateWidget("TSMLabel", parent, args)
labelWidget:SetColor(args.colorRed, args.colorGreen, args.colorBlue)
return labelWidget
end,
MultiLabel = function(parent, args)
local labelWidget = CreateWidget("TSMMultiLabel", parent, args)
labelWidget:SetLabels(args.labelInfo)
return labelWidget
end,
InteractiveLabel = function(parent, args)
local iLabelWidget = CreateWidget("TSMInteractiveLabel", parent, args)
iLabelWidget:SetCallback("OnClick", args.callback)
return iLabelWidget
end,
Button = function(parent, args)
local buttonWidget = CreateWidget("TSMButton", parent, args)
buttonWidget:SetCallback("OnClick", args.callback)
return buttonWidget
end,
GroupItemList = function(parent, args)
local groupItemList = CreateWidget("TSMGroupItemList", parent, args)
groupItemList:SetIgnoreVisible(args.showIgnore)
groupItemList:SetTitle("left", args.leftTitle)
groupItemList:SetTitle("right", args.rightTitle)
groupItemList:SetListCallback(args.listCallback)
groupItemList:SetCallback("OnAddClicked", args.onAdd)
groupItemList:SetCallback("OnRemoveClicked", args.onRemove)
return groupItemList
end,
MacroButton = function(parent, args)
local macroButtonWidget = CreateWidget("TSMMacroButton", parent, args)
macroButtonWidget.frame:SetAttribute("type", "macro")
macroButtonWidget.frame:SetAttribute("macrotext", args.macroText)
return macroButtonWidget
end,
EditBox = function(parent, args)
local editBoxWidget = CreateWidget("TSMEditBox", parent, args)
editBoxWidget:SetText(args.value)
editBoxWidget:DisableButton(args.onTextChanged)
editBoxWidget:SetAutoComplete(args.autoComplete)
local function callback(self, event, value)
if args.acceptCustom then
local badPriceSource = type(args.acceptCustom) == "string" and strlower(args.acceptCustom)
local customPrice, err = TSMAPI:ParseCustomPrice(value, badPriceSource)
if customPrice then
self:SetText(FormatCopperCustomPrice(value))
self:ClearFocus()
args.callback(self, event, value)
else
TSM:Print(L["Invalid custom price."].." "..err)
self:SetFocus()
end
else
args.callback(self, event, value)
end
end
editBoxWidget:SetCallback(args.onTextChanged and "OnTextChanged" or "OnEnterPressed", callback)
if args.acceptCustom then
customPriceFrame = customPriceFrame or CreateCustomPriceFrame()
editBoxWidget:SetCallback("OnEditFocusGained", function() customPriceFrame:Show() end)
editBoxWidget:SetCallback("OnEditFocusLost", function() customPriceFrame:Hide() end)
end
return editBoxWidget
end,
GroupBox = function(parent, args)
local groupBoxWidget = CreateWidget("TSMGroupBox", parent, args)
groupBoxWidget:SetText(args.value)
groupBoxWidget:SetCallback("OnValueChanged", args.callback)
return groupBoxWidget
end,
CheckBox = function(parent, args)
local checkBoxWidget = CreateWidget("TSMCheckBox", parent, args)
checkBoxWidget:SetType(args.cbType or "checkbox")
checkBoxWidget:SetValue(args.value)
if args.label then checkBoxWidget:SetLabel(args.label) end
if not args.width and not args.relativeWidth then
checkBoxWidget:SetRelativeWidth(0.5)
end
checkBoxWidget:SetCallback("OnValueChanged", args.callback)
return checkBoxWidget
end,
Slider = function(parent, args)
local sliderWidget = CreateWidget("TSMSlider", parent, args)
sliderWidget:SetValue(args.value)
sliderWidget:SetSliderValues(args.min, args.max, args.step)
sliderWidget:SetIsPercent(args.isPercent)
sliderWidget:SetCallback("OnValueChanged", args.callback)
return sliderWidget
end,
Icon = function(parent, args)
local iconWidget = CreateWidget("Icon", parent, args)
iconWidget:SetImage(args.image)
iconWidget:SetImageSize(args.imageWidth, args.imageHeight)
iconWidget:SetCallback("OnClick", args.callback)
return iconWidget
end,
Dropdown = function(parent, args)
local dropdownWidget = CreateWidget("TSMDropdown", parent, args)
dropdownWidget:SetList(args.list, args.order)
dropdownWidget:SetMultiselect(args.multiselect)
if type(args.value) == "table" then
for name, value in pairs(args.value) do
dropdownWidget:SetItemValue(name, value)
end
else
dropdownWidget:SetValue(args.value)
end
dropdownWidget:SetCallback("OnValueChanged", args.callback)
return dropdownWidget
end,
ColorPicker = function(parent, args)
local colorPicker = CreateWidget("TSMColorPicker", parent, args)
colorPicker:SetHasAlpha(args.hasAlpha)
if type(args.value) == "table" then
colorPicker:SetColor(unpack(args.value))
end
colorPicker:SetCallback("OnValueChanged", args.callback)
colorPicker:SetCallback("OnValueConfirmed", args.callback)
return colorPicker
end,
Spacer = function(parent, args)
args.quantity = args.quantity or 1
for i=1, args.quantity do
local spacer = parent:Add({type="Label", text=" ", relativeWidth=1})
end
end,
HeadingLine = function(parent, args)
local heading = AceGUI:Create("Heading")
heading:SetText("")
heading:SetRelativeWidth(args.relativeWidth or 1)
parent:AddChild(heading)
end,
}
-- creates a widget or container as detailed in the passed table (iTable) and adds it as a child of the passed parent
function lib.AddGUIElement(parent, iTable)
assert(Add[iTable.type], "Invalid Widget or Container Type: "..iTable.type)
return Add[iTable.type](parent, iTable)
end
-- goes through a page-table and draws out all the containers and widgets for that page
function TSMAPI:BuildPage(oContainer, oPageTable, noPause)
local function recursive(container, pageTable)
for _, data in pairs(pageTable) do
local parentElement = container:Add(data)
if data.children then
parentElement:PauseLayout()
-- yay recursive function calls!
recursive(parentElement, data.children)
parentElement:ResumeLayout()
parentElement:DoLayout()
end
end
end
if not oContainer.Add then
local container = AceGUI:Create("TSMSimpleGroup")
container:SetLayout("fill")
container:SetFullWidth(true)
container:SetFullHeight(true)
oContainer:AddChild(container)
oContainer = container
end
if not noPause then
oContainer:PauseLayout()
recursive(oContainer, oPageTable)
oContainer:ResumeLayout()
oContainer:DoLayout()
else
recursive(oContainer, oPageTable)
end
end

128
TradeSkillMaster/GUI/Design.lua

@ -0,0 +1,128 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains support code for the custom TSM widgets
local TSM = select(2, ...)
local lib = TSMAPI
TSMAPI.Design = {}
local Design = TSMAPI.Design
local coloredFrames = {}
local coloredTexts = {}
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function expandColor(tbl)
tbl = CopyTable(tbl)
for i=1, 3 do
tbl[i] = tbl[i] / 255
end
return unpack(tbl)
end
local function SetFrameColor(obj, colorKey)
local color = TSM.db.profile.design.frameColors[colorKey]
if not obj then return expandColor(color.backdrop) end
coloredFrames[obj] = {obj, colorKey}
if obj:IsObjectType("Frame") then
obj:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=TSM.db.profile.design.edgeSize})
obj:SetBackdropColor(expandColor(color.backdrop))
obj:SetBackdropBorderColor(expandColor(color.border))
else
obj:SetTexture(expandColor(color.backdrop))
end
end
local function SetTextColor(obj, colorKey, isDisabled)
local color = TSM.db.profile.design.textColors[colorKey]
if not obj then return expandColor(color.enabled) end
coloredTexts[obj] = {obj, colorKey, isDisabled}
if obj:IsObjectType("Texture") then
obj:SetTexture(expandColor(color.enabled))
else
if isDisabled then
obj:SetTextColor(expandColor(color.disabled))
else
obj:SetTextColor(expandColor(color.enabled))
end
end
end
--[[-----------------------------------------------------------------------------
Design API functions
-------------------------------------------------------------------------------]]
function Design:SetFrameBackdropColor(obj)
return SetFrameColor(obj, "frameBG")
end
function Design:SetFrameColor(obj)
return SetFrameColor(obj, "frame")
end
function Design:SetContentColor(obj)
return SetFrameColor(obj, "content")
end
function Design:SetIconRegionColor(obj)
return SetTextColor(obj, "iconRegion")
end
function Design:SetWidgetTextColor(obj, isDisabled)
return SetTextColor(obj, "text", isDisabled)
end
function Design:SetWidgetLabelColor(obj, isDisabled)
return SetTextColor(obj, "label", isDisabled)
end
function Design:SetTitleTextColor(obj)
return SetTextColor(obj, "title")
end
function Design:GetContentFont(size)
size = size or "normal"
TSM.db.profile.design.fontSizes[size] = TSM.db.profile.design.fontSizes[size] or TSM.designDefaults.fontSizes[size]
assert(TSM.db.profile.design.fontSizes[size], format("Invalid font size '%s", tostring(size)))
return TSM.db.profile.design.fonts.content, TSM.db.profile.design.fontSizes[size]
end
function Design:GetBoldFont()
return TSM.db.profile.design.fonts.bold
end
function Design:GetInlineColor(key)
TSM.db.profile.design.inlineColors[key] = TSM.db.profile.design.inlineColors[key] or CopyTable(TSM.designDefaults.inlineColors[key])
local r, g, b, a = unpack(TSM.db.profile.design.inlineColors[key])
return format("|c%02X%02X%02X%02X", a, r, g, b)
end
function Design:ColorText(text, key)
local color = Design:GetInlineColor(key)
return color..text.."|r"
end
function TSMAPI:UpdateDesign()
-- set any missing fields
TSM:SetDesignDefaults(TSM.designDefaults, TSM.db.profile.design)
local oldTbl = coloredFrames
coloredFrames = {}
for _, args in pairs(oldTbl) do
SetFrameColor(unpack(args))
end
oldTbl = coloredTexts
coloredTexts = {}
for _, args in pairs(oldTbl) do
SetTextColor(unpack(args))
end
end

373
TradeSkillMaster/GUI/GroupTree.lua

@ -0,0 +1,373 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local COUNT = 1
local ROW_HEIGHT = 14
local function UpdateTree(self)
self.statusText:SetText("")
local rowData = {}
local groupPathList, disabledGroupPaths = TSM:GetGroupPathList(self.module)
for i, groupPath in ipairs(groupPathList) do
if not disabledGroupPaths[groupPath] then
local pathParts = { TSM.GROUP_SEP:split(groupPath) }
local leader = ""
for i = 1, #pathParts - 1 do
leader = leader .. " "
end
local hasSubGroups = (groupPathList[i + 1] and (groupPathList[i + 1] == groupPath or strfind(groupPathList[i + 1], "^" .. TSMAPI:StrEscape(groupPath) .. TSM.GROUP_SEP)))
local parent = #pathParts > 1 and table.concat(pathParts, TSM.GROUP_SEP, 1, #pathParts - 1) or nil
local index = #rowData + 1
if self.selectedGroups[groupPath] == nil then
-- select group by default
self.selectedGroups[groupPath] = true
end
local groupNameText = pathParts[#pathParts]
if TSM.db.profile.colorGroupName then
groupNameText = TSMAPI:ColorGroupName(groupNameText, #pathParts)
end
rowData[index] = {
value = leader .. format("%s %s%s|r", groupNameText, TSMAPI.Design:GetInlineColor("link"), hasSubGroups and (self.collapsed[groupPath] and "[+]" or "[-]") or ""),
groupName = pathParts[#pathParts],
parent = parent,
groupPath = groupPath,
hasSubGroups = hasSubGroups,
index = index,
isSelected = not self.isGroupBox and self.selectedGroups[groupPath], -- select all rows by default (unless it's for a GroupBox)
}
end
end
if #rowData == 0 then
if #groupPathList == 0 then
self.statusText:SetText(L["You currently don't have any groups setup. Type '/tsm' and click on the 'TradeSkillMaster Groups' button to setup TSM groups."])
else
self.statusText:SetText(format(L["None of your groups have %s operations assigned. Type '/tsm' and click on the 'TradeSkillMaster Groups' button to assign operations to your TSM groups."], self.module))
end
else
self.statusText:SetText("")
end
self.rowData = rowData
self:RefreshRows()
end
local function SelectAll(self)
for i = 1, #self.st.rowData do
self.st.selectedGroups[self.st.rowData[i].groupPath] = true
self.st.rowData[i].isSelected = true
end
self.st:RefreshRows()
for _, row in ipairs(self.st.rows) do
row.highlight:Show()
end
end
local function DeselectAll(self)
for i = 1, #self.st.rowData do
self.st.selectedGroups[self.st.rowData[i].groupPath] = false
self.st.rowData[i].isSelected = nil
end
self.st:RefreshRows()
for _, row in ipairs(self.st.rows) do
row.highlight:Hide()
end
end
local methods = {
GetRowIndex = function(self, value)
for i, v in pairs(self.rowData) do
if v.groupPath == value then
return i
end
end
end,
RefreshRows = function(self)
local offset = FauxScrollFrame_GetOffset(self.scrollFrame)
self.offset = offset
for i = #self.rowData, 1, -1 do
local data = self.rowData[i]
if not self.isGroupBox and not data.isSelected and data.parent then
local index = self:GetRowIndex(data.parent)
if index then
self.rowData[index].isSelected = self.selectedGroups[self.rowData[index].groupPath]
end
end
end
local displayRows = {}
for i = 1, #self.rowData do
local pathParts = { TSM.GROUP_SEP:split(self.rowData[i].groupPath) }
local isCollapsed = false
for i = 1, #pathParts - 1 do
local path = table.concat(pathParts, TSM.GROUP_SEP, 1, i)
if self.collapsed[path] then
isCollapsed = true
break
end
end
if not isCollapsed then
if self.collapsed[self.rowData[i].groupPath] then
self.rowData[i].value = gsub(self.rowData[i].value, TSMAPI:StrEscape("[-]"), "[+]")
else
self.rowData[i].value = gsub(self.rowData[i].value, TSMAPI:StrEscape("[+]"), "[-]")
end
tinsert(displayRows, self.rowData[i])
end
end
FauxScrollFrame_Update(self.scrollFrame, #displayRows, self.NUM_ROWS, ROW_HEIGHT)
for i = 1, self.NUM_ROWS do
if i > #displayRows then
self.rows[i]:Hide()
self.rows[i].data = nil
else
self.rows[i]:Show()
local data = displayRows[i + offset]
if not data then return end
self.rows[i].data = data
if data.isSelected or self.rows[i]:IsMouseOver() then
self.rows[i].highlight:Show()
else
self.rows[i].highlight:Hide()
end
self.rows[i]:SetText(data.value)
end
end
end,
SetSelection = function(self, rowNum, isSelected)
self.selectedGroups[self.rowData[rowNum].groupPath] = isSelected or false
self.rowData[rowNum].isSelected = isSelected
self:RefreshRows()
end,
GetSelectedGroupInfo = function(self, rowNum)
local groupInfo = {}
for _, data in ipairs(self.rowData) do
if data.isSelected then
groupInfo[data.groupPath] = { operations = TSM:GetGroupOperations(data.groupPath, self.module), items = TSM:GetGroupItems(data.groupPath) }
if self.module and not groupInfo[data.groupPath].operations then
groupInfo[data.groupPath] = nil
end
end
end
return groupInfo
end,
ClearSelection = function(self)
for i = 1, #self.rowData do
self.selectedGroups[self.rowData[i].groupPath] = false
self.rowData[i].isSelected = nil
end
self.groupBoxSelection = nil
self:RefreshRows()
end,
SetGropBoxSelection = function(self, groupPath)
if self.groupBoxSelection then
self.groupBoxSelection.isSelected = nil
self.groupBoxSelection = nil
end
for i = 1, #self.rowData do
if self.rowData[i].groupPath == groupPath then
self.rowData[i].isSelected = true
self.groupBoxSelection = self.rowData[i]
break
end
end
end,
GetGroupBoxSelection = function(self)
return self.groupBoxSelection and self.groupBoxSelection.groupPath
end,
}
local defaultColScripts = {
OnEnter = function(self)
local tooltipLines = {}
tinsert(tooltipLines, format(L["%sLeft-Click|r to select / deselect this group."], TSMAPI.Design:GetInlineColor("link")))
if self.data.hasSubGroups then
tinsert(tooltipLines, format(L["%sRight-Click|r to collapse / expand this group."], TSMAPI.Design:GetInlineColor("link")))
end
local operations = TSM:GetGroupOperations(self.data.groupPath, self.st.module)
local operationLine = operations and table.concat(operations, ", ") or L["<No Operation>"]
tinsert(tooltipLines, "")
tinsert(tooltipLines, format(L["Operations: %s"], operationLine))
GameTooltip:SetOwner(self, "ANCHOR_TOP")
GameTooltip:AddLine(table.concat(tooltipLines, "\n"), 1, 1, 1)
GameTooltip:Show()
self.highlight:Show()
end,
OnLeave = function(self)
GameTooltip:Hide()
if not self.data.isSelected then
self.highlight:Hide()
end
end,
OnClick = function(self, button)
if button == "RightButton" then
self.st.collapsed[self.data.groupPath] = not self.st.collapsed[self.data.groupPath]
self.st:RefreshRows()
return
end
if self.st.isGroupBox then
if self.data ~= self.st.groupBoxSelection then
if self.st.groupBoxSelection then
self.st.groupBoxSelection.isSelected = false
end
self.st.groupBoxSelection = self.data
end
self.data.isSelected = true
else
self.data.isSelected = not self.data.isSelected
self.st.selectedGroups[self.data.groupPath] = self.data.isSelected or false
if self.data.hasSubGroups then
for i = 1, #self.st.rowData do
if self.st.rowData[i].groupPath == self.data.groupPath or strfind(self.st.rowData[i].groupPath, "^" .. TSMAPI:StrEscape(self.data.groupPath) .. TSM.GROUP_SEP) then
self.st.selectedGroups[self.st.rowData[i].groupPath] = self.data.isSelected or false
self.st.rowData[i].isSelected = self.data.isSelected
end
end
end
end
self.st:RefreshRows()
if self.data.isSelected then
self.highlight:Show()
else
self.highlight:Hide()
end
end,
}
function TSMAPI:CreateGroupTree(parent, module, label, isGroupBox)
assert(type(parent) == "table", format(L["Invalid parent argument type. Expected table, got %s."], type(parent)))
local name = "TSMGroupTree" .. COUNT
COUNT = COUNT + 1
local st = CreateFrame("Frame", name, parent)
st:SetAllPoints()
st:SetScript("OnShow", UpdateTree)
st.NUM_ROWS = floor((parent:GetHeight() - (isGroupBox and 0 or 20)) / ROW_HEIGHT)
st.isGroupBox = isGroupBox
st.groupBoxSelection = nil
st.module = module
if label or module then
label = label or module
if not TSM.db.profile.groupTreeSelectedGroupStatus[label] then
TSMAPI:CreateTimeDelay(0, function() SelectAll({st=st}) end)
end
TSM.db.profile.groupTreeCollapsedStatus[label] = TSM.db.profile.groupTreeCollapsedStatus[label] or {}
TSM.db.profile.groupTreeSelectedGroupStatus[label] = TSM.db.profile.groupTreeSelectedGroupStatus[label] or {}
st.collapsed = TSM.db.profile.groupTreeCollapsedStatus[label]
st.selectedGroups = TSM.db.profile.groupTreeSelectedGroupStatus[label]
else
st.collapsed = {}
st.selectedGroups = {}
end
local contentFrame = CreateFrame("Frame", name .. "Content", st)
contentFrame:SetPoint("TOPLEFT")
contentFrame:SetPoint("BOTTOMRIGHT", -15, isGroupBox and 0 or 18)
st.contentFrame = contentFrame
if not isGroupBox then
local btn = TSMAPI.GUI:CreateButton(st, 14)
btn:SetPoint("BOTTOMLEFT", 0, 2)
btn:SetPoint("BOTTOMRIGHT", st, "BOTTOM", -2, 2)
btn:SetHeight(16)
btn:SetText(L["Select All Groups"])
btn:SetScript("OnClick", SelectAll)
btn.st = st
local btn = TSMAPI.GUI:CreateButton(st, 14)
btn:SetPoint("BOTTOMLEFT", st, "BOTTOM", 2, 2)
btn:SetPoint("BOTTOMRIGHT", 0, 2)
btn:SetHeight(16)
btn:SetText(L["Deselect All Groups"])
btn:SetScript("OnClick", DeselectAll)
btn.st = st
end
-- frame to hold the rows
local scrollFrame = CreateFrame("ScrollFrame", name .. "ScrollFrame", st, "FauxScrollFrameTemplate")
scrollFrame:SetScript("OnVerticalScroll", function(self, offset)
FauxScrollFrame_OnVerticalScroll(self, offset, ROW_HEIGHT, function() st:RefreshRows() end)
end)
scrollFrame:SetAllPoints(contentFrame)
st.scrollFrame = scrollFrame
-- make the scroll bar consistent with the TSM theme
local scrollBar = _G[scrollFrame:GetName() .. "ScrollBar"]
scrollBar:ClearAllPoints()
scrollBar:SetPoint("BOTTOMRIGHT", st, -1, isGroupBox and 1 or 19)
scrollBar:SetPoint("TOPRIGHT", st, -1, -1)
scrollBar:SetWidth(12)
local thumbTex = scrollBar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetContentColor(thumbTex)
thumbTex:SetHeight(50)
thumbTex:SetWidth(scrollBar:GetWidth())
_G[scrollBar:GetName() .. "ScrollUpButton"]:Hide()
_G[scrollBar:GetName() .. "ScrollDownButton"]:Hide()
local text = st:CreateFontString()
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
text:SetJustifyH("CENTER")
text:SetJustifyV("CENTER")
text:SetPoint("LEFT", 5, 0)
text:SetPoint("RIGHT", -5, 0)
text:SetHeight(100)
text:SetNonSpaceWrap(true)
st.statusText = text
-- create the rows
st.rows = {}
for i = 1, st.NUM_ROWS do
local row = CreateFrame("Button", name .. "Row" .. i, st.contentFrame)
row:SetHeight(ROW_HEIGHT)
row:RegisterForClicks("AnyUp")
if i == 1 then
row:SetPoint("TOPLEFT")
row:SetPoint("TOPRIGHT")
else
row:SetPoint("TOPLEFT", st.rows[i - 1], "BOTTOMLEFT")
row:SetPoint("TOPRIGHT", st.rows[i - 1], "BOTTOMRIGHT")
end
local highlight = row:CreateTexture()
highlight:SetAllPoints()
highlight:SetTexture(1, .9, 0, .2)
highlight:Hide()
row.highlight = highlight
local text = row:CreateFontString()
text:SetFont(TSMAPI.Design:GetContentFont("medium"))
text:SetJustifyH("LEFT")
text:SetJustifyV("CENTER")
text:SetPoint("TOPLEFT", 1, -1)
text:SetPoint("BOTTOMRIGHT", -1, 1)
row:SetFontString(text)
for name, func in pairs(defaultColScripts) do
row:SetScript(name, func)
end
row.st = st
tinsert(st.rows, row)
end
for name, func in pairs(methods) do
st[name] = func
end
UpdateTree(st)
return st
end

338
TradeSkillMaster/GUI/Gui.lua

@ -0,0 +1,338 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This module holds some GUI helper functions for modules to use.
local TSM = select(2, ...)
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Gui_private")
private.frames = {}
TSMAPI.GUI = {}
-- Tooltips!
local function ShowTooltip(self)
if self.link then
GameTooltip:SetOwner(self, "ANCHOR_TOPRIGHT")
TSMAPI:SafeTooltipLink(self.link)
GameTooltip:Show()
elseif type(self.tooltip) == "function" then
local text = self.tooltip(self)
if type(text) == "string" then
GameTooltip:SetOwner(self, "ANCHOR_TOP")
--GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetText(text, 1, 1, 1, 1, true)
GameTooltip:Show()
end
elseif self.tooltip then
GameTooltip:SetOwner(self, "ANCHOR_TOP")
--GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetText(self.tooltip, 1, 1, 1, 1, true)
GameTooltip:Show()
elseif self.frame.tooltip then
GameTooltip:SetOwner(self.frame, "ANCHOR_TOP")
--GameTooltip:SetOwner(self.frame, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetText(self.frame.tooltip, 1, 1, 1, 1, true)
GameTooltip:Show()
end
end
local function HideTooltip()
-- BattlePetTooltip:Hide()
GameTooltip:Hide()
end
function TSMAPI.GUI:CreateButton(parent, textHeight, name, isSecure)
local btn = CreateFrame("Button", name, parent, isSecure and "SecureActionButtonTemplate")
TSMAPI.Design:SetContentColor(btn)
local highlight = btn:CreateTexture(nil, "HIGHLIGHT")
highlight:SetAllPoints()
highlight:SetTexture(1, 1, 1, .2)
highlight:SetBlendMode("BLEND")
btn.highlight = highlight
btn:SetScript("OnEnter", function(self) if self.tooltip then ShowTooltip(self) end end)
btn:SetScript("OnLeave", HideTooltip)
btn:Show()
local label = btn:CreateFontString()
label:SetFont(TSMAPI.Design:GetContentFont(), textHeight)
label:SetPoint("CENTER")
label:SetJustifyH("CENTER")
label:SetJustifyV("CENTER")
label:SetHeight(textHeight)
TSMAPI.Design:SetWidgetTextColor(label)
btn:SetFontString(label)
TSM:Hook(btn, "Enable", function() TSMAPI.Design:SetWidgetTextColor(label) end, true)
TSM:Hook(btn, "Disable", function() TSMAPI.Design:SetWidgetTextColor(label, true) end, true)
return btn
end
function TSMAPI.GUI:CreateHorizontalLine(parent, ofsy, relativeFrame, invertedColor)
relativeFrame = relativeFrame or parent
local barTex = parent:CreateTexture()
barTex:SetPoint("TOPLEFT", relativeFrame, "TOPLEFT", 2, ofsy)
barTex:SetPoint("TOPRIGHT", relativeFrame, "TOPRIGHT", -2, ofsy)
barTex:SetHeight(2)
if invertedColor then
TSMAPI.Design:SetFrameColor(barTex)
else
TSMAPI.Design:SetContentColor(barTex)
end
return barTex
end
function TSMAPI.GUI:CreateVerticalLine(parent, ofsx, relativeFrame, invertedColor)
relativeFrame = relativeFrame or parent
local barTex = parent:CreateTexture()
barTex:SetPoint("TOPLEFT", relativeFrame, "TOPLEFT", ofsx, -2)
barTex:SetPoint("BOTTOMLEFT", relativeFrame, "BOTTOMLEFT", ofsx, 2)
barTex:SetWidth(2)
if invertedColor then
TSMAPI.Design:SetFrameColor(barTex)
else
TSMAPI.Design:SetContentColor(barTex)
end
return barTex
end
function TSMAPI.GUI:CreateInputBox(parent, name, autoComplete)
local function OnEscapePressed(self)
self:ClearFocus()
self:HighlightText(0, 0)
end
local eb = CreateFrame("EditBox", name, parent)
eb:SetFont(TSMAPI.Design:GetContentFont("normal"))
eb:SetShadowColor(0, 0, 0, 0)
TSMAPI.Design:SetContentColor(eb)
eb:SetAutoFocus(false)
eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() self:HighlightText(0, 0) end)
eb:SetScript("OnEnter", function(self) if self.tooltip then ShowTooltip(self) end end)
eb:SetScript("OnLeave", HideTooltip)
return eb
end
function TSMAPI.GUI:SetAutoComplete(inputBox, params)
local autoCompleteHandlers = {"OnTabPressed", "OnEnterPressed", "OnTextChanged", "OnChar", "OnEditFocusLost", "OnEscapePressed"}--, "OnArrowPressed"}
if params then
if inputBox._priorTSMHandlers then return end -- already done
inputBox.autoCompleteParams = params
inputBox._priorTSMHandlers = {}
for _, name in ipairs(autoCompleteHandlers) do
inputBox._priorTSMHandlers[name] = inputBox:GetScript(name)
inputBox:SetScript(name, function(self, ...) return _G["AutoCompleteEditBox_"..name](self, ...) or (self._priorTSMHandlers[name] and self._priorTSMHandlers[name](self, ...)) end)
end
else
if not inputBox._priorTSMHandlers then return end -- already done
for _, name in ipairs(autoCompleteHandlers) do
inputBox:SetScript(name, inputBox._priorTSMHandlers[name])
end
inputBox._priorTSMHandlers = nil
end
end
function TSMAPI.GUI:CreateLabel(parent, size)
local label = parent:CreateFontString()
label:SetFont(TSMAPI.Design:GetContentFont(size))
TSMAPI.Design:SetWidgetLabelColor(label)
return label
end
function TSMAPI.GUI:CreateTitleLabel(parent, size)
local label = parent:CreateFontString()
label:SetFont(TSMAPI.Design:GetBoldFont(), size)
TSMAPI.Design:SetTitleTextColor(label)
return label
end
function TSMAPI.GUI:CreateStatusBar(parent, baseName)
local function UpdateStatus(self, majorStatus, minorStatus)
if majorStatus then
self.majorStatusBar:SetValue(majorStatus)
if majorStatus == 100 then
self.majorStatusBar.ag:Stop()
elseif not self.majorStatusBar.ag:IsPlaying() then
self.majorStatusBar.ag:Play()
end
end
if minorStatus then
self.minorStatusBar:SetValue(minorStatus)
if minorStatus == 100 then
self.minorStatusBar.ag:Stop()
elseif not self.minorStatusBar.ag:IsPlaying() then
self.minorStatusBar.ag:Play()
end
end
end
local function SetStatusText(self, text)
self.text:SetText(text)
end
local level = parent:GetFrameLevel()
local frame = CreateFrame("Frame", nil, parent)
frame:SetHeight(25)
frame:SetPoint("TOPLEFT", 2, -3)
frame:SetPoint("TOPRIGHT", -2, -3)
frame:SetFrameLevel(level+1)
frame.UpdateStatus = UpdateStatus
frame.SetStatusText = SetStatusText
-- minor status bar (gray one)
local statusBar = CreateFrame("STATUSBAR", baseName.."-Minor", frame, "TextStatusBar")
statusBar:SetOrientation("HORIZONTAL")
statusBar:SetMinMaxValues(0, 100)
statusBar:SetAllPoints()
statusBar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
statusBar:SetStatusBarColor(.42, .42, .42, .7)
statusBar:SetFrameLevel(level+2)
local ag = statusBar:CreateAnimationGroup()
local alpha = ag:CreateAnimation("Alpha")
alpha:SetDuration(1)
alpha:SetChange(-.5)
ag:SetLooping("Bounce")
statusBar.ag = ag
frame.minorStatusBar = statusBar
-- major status bar (main blue one)
local statusBar = CreateFrame("STATUSBAR", baseName.."-Major", frame, "TextStatusBar")
statusBar:SetOrientation("HORIZONTAL")
statusBar:SetMinMaxValues(0, 100)
statusBar:SetAllPoints()
statusBar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
statusBar:SetStatusBarColor(.19, .22, .33, .9)
statusBar:SetFrameLevel(level+3)
local ag = statusBar:CreateAnimationGroup()
local alpha = ag:CreateAnimation("Alpha")
alpha:SetDuration(1)
alpha:SetChange(-.5)
ag:SetLooping("Bounce")
statusBar.ag = ag
frame.majorStatusBar = statusBar
local textFrame = CreateFrame("Frame", nil, frame)
textFrame:SetFrameLevel(level+4)
textFrame:SetAllPoints(frame)
-- Text for the StatusBar
local text = TSMAPI.GUI:CreateLabel(textFrame)
TSMAPI.Design:SetWidgetTextColor(text)
text:SetPoint("CENTER")
frame.text = text
return frame
end
function TSMAPI.GUI:CreateDropdown(parent, list, tooltip)
local dd = LibStub("AceGUI-3.0"):Create("TSMDropdown")
dd:SetDisabled()
dd:SetMultiselect(false)
dd:SetList(list)
dd.frame:SetParent(parent)
dd.frame:Show()
dd.frame.tooltip = tooltip
dd:SetCallback("OnEnter", ShowTooltip)
dd:SetCallback("OnLeave", HideTooltip)
return dd
end
function TSMAPI.GUI:CreateCheckBox(parent, tooltip)
local cb = LibStub("AceGUI-3.0"):Create("TSMCheckBox")
cb.frame:SetParent(parent)
cb.frame:Show()
cb.frame.tooltip = tooltip
cb:SetCallback("OnEnter", ShowTooltip)
cb:SetCallback("OnLeave", HideTooltip)
return cb
end
function TSMAPI.GUI:CreateItemLinkLabel(parent, textHeight)
local btn = CreateFrame("Button", nil, parent)
btn:SetScript("OnEnter", function(self) if self.link then ShowTooltip(self) end end)
btn:SetScript("OnLeave", HideTooltip)
btn:SetScript("OnClick", function(self) if self.link then HandleModifiedItemClick(self.link) end end)
btn:SetHeight(textHeight)
btn:Show()
local text = btn:CreateFontString()
text:SetFont(TSMAPI.Design:GetContentFont(), textHeight)
text:SetPoint("TOPLEFT")
text:SetPoint("BOTTOMLEFT")
text:SetJustifyH("LEFT")
text:SetJustifyV("CENTER")
btn:SetFontString(text)
return btn
end
-- Registers a movable/resizable frame which TSM will keep track of and persistently store its position / size.
-- The frame must be named for this function to work.
-- Required defaults
-- x - x position
-- y - y position
-- width - width
-- height - height
-- scale - scale
function TSMAPI:CreateMovableFrame(name, defaults, parent)
local options = TSM.db.global.frameStatus[name] or CopyTable(defaults)
options.defaults = defaults
TSM.db.global.frameStatus[name] = options
options.hasLoaded = nil
local frame = CreateFrame("Frame", name, parent)
frame:Hide()
frame:SetHeight(options.height)
frame:SetWidth(options.width)
frame:SetScale(UIParent:GetScale()*options.scale)
frame:SetPoint("CENTER", frame:GetParent())
frame:SetToplevel(true)
frame:EnableMouse(true)
frame:SetMovable(true)
frame:SetClampedToScreen(true)
frame:SetClampRectInsets(options.width-50, -(options.width-50), -(options.height-50), options.height-50)
frame.SavePositionAndSize = function(self)
if not options.hasLoaded then return end
options.width = self:GetWidth()
options.height = self:GetHeight()
options.x = self:GetLeft()
options.y = self:GetBottom()
self:SetClampRectInsets(options.width-50, -(options.width-50), -(options.height-50), options.height-50)
end
frame:SetScript("OnMouseDown", frame.StartMoving)
frame:SetScript("OnMouseUp", function(self) self:StopMovingOrSizing() self:SavePositionAndSize() end)
frame:SetScript("OnSizeChanged", frame.SavePositionAndSize)
frame.RefreshPosition = function(self)
options.hasLoaded = true
self:SetScale(UIParent:GetScale()*options.scale)
self:SetFrameLevel(0)
self:ClearAllPoints()
self:SetPoint("BOTTOMLEFT", UIParent, options.x, options.y)
self:SetWidth(options.width)
self:SetHeight(options.height)
end
frame:SetScript("OnShow", frame.RefreshPosition)
frame.options = options
tinsert(private.frames, frame)
return frame
end
function TSM:ResetFrames()
for _, frame in ipairs(private.frames) do
-- reset all fields to the default values without breaking any table references
local options = TSM.db.global.frameStatus[frame:GetName()]
options.hasLoaded = true
local defaults = options.defaults
for i, v in pairs(defaults) do options[i] = v end
if frame and frame:IsVisible() then
frame:RefreshPosition()
end
end
-- explicitly reset bankui since it can't easily use TSMAPI:CreateMovableFrame
TSM:ResetBankUIFramePosition()
end

140
TradeSkillMaster/GUI/MainFrame.lua

@ -0,0 +1,140 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains all the APIs regarding TSM's main frame (what shows when you type '/tsm').
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local private = {icons={}, currentIcon=0}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.MainFrame_private")
local lib = TSMAPI
--- Opens the main TSM window.
function TSMAPI:OpenFrame()
if not TSM.Frame then return end
TSM.Frame:Show()
if #TSM.Frame.children > 0 then
TSM.Frame:ReleaseChildren()
else
TSMAPI:SelectIcon("TradeSkillMaster", L["TSM Status / Options"])
end
if TSM.db.global.infoMessage < 1001 then
TSM.db.global.infoMessage = 1001
StaticPopupDialogs["TSM_INFO_MESSAGE"] = StaticPopupDialogs["TSM_INFO_MESSAGE"] or {
text = format(L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced")),
button1 = OKAY,
OnAccept = function()
TSM:Printf(L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced"))
end,
timeout = 0,
preferredIndex = 3,
}
TSMAPI:ShowStaticPopupDialog("TSM_INFO_MESSAGE")
end
end
--- Closes the main TSM window.
function TSMAPI:CloseFrame()
TSM.Frame:Hide()
end
function TSM:RegisterMainFrameIcon(displayName, icon, loadGUI, moduleName, side)
if not (displayName and icon and loadGUI and moduleName) then
return nil, "invalid args", displayName, icon, loadGUI, moduleName
end
if side and not (side == "module" or side == "options") then
return nil, "invalid side", side
end
local icon = {name=displayName, moduleName=moduleName, icon=icon, loadGUI=loadGUI, side=(strlower(side or "module"))}
if TSM.Frame then
icon.texture = icon.icon
if icon.side == "options" then
icon.where = "topLeft"
else
icon.where = "topRight"
end
TSM.Frame:AddIcon(icon)
end
tinsert(private.icons, icon)
end
--- Selects an icon in the main TSM window once it's open.
-- @param moduleName Which module the icon belongs to (unlocalized).
-- @param iconName The text that shows in the tooltip of the icon to be clicked (localized).
-- @return Returns an error message as the second return value upon error.
function TSMAPI:SelectIcon(moduleName, iconName)
if not moduleName then return nil, "no moduleName passed" end
for _, data in ipairs(private.icons) do
if data.moduleName == moduleName and data.name == iconName then
data.frame:Click()
end
end
end
function TSMAPI:ShowOperationOptions(moduleName, operation, groupPath)
TSMAPI:OpenFrame()
TSM.loadModuleOptionsTab = {module=moduleName, operation=operation, group=groupPath}
TSMAPI:SelectIcon("TradeSkillMaster", L["Module Operations / Options"])
TSM.loadModuleOptionsTab = nil
end
function TSM:CreateMainFrame()
local mainFrame = AceGUI:Create("TSMMainFrame")
local version = TSM._version
mainFrame:SetIconText(version)
mainFrame:SetIconLabels(L["Options"], L["Modules"])
mainFrame:SetLayout("Fill")
for _, icon in ipairs(private.icons) do
icon.texture = icon.icon
if icon.side == "crafting" then
icon.where = "bottom"
elseif icon.side == "options" then
icon.where = "topLeft"
else
icon.where = "topRight"
end
mainFrame:AddIcon(icon)
end
TSM.Frame = mainFrame
TSMAPI:CreateTimeDelay("mainFrameSize", .5, function() mainFrame:SetWidth(mainFrame.frame.options.width) mainFrame:SetHeight(mainFrame.frame.options.height) end)
end
function TSM:TSMFrameIsVisible()
return TSM.Frame and TSM.Frame:IsVisible()
end
function private:FramePathHelper(frame, path)
if not frame.children or not frame.children[1] then return end
frame = frame.children[1]
if frame.type == "TSMTreeGroup" then
tinsert(path, {type="TreeGroup", value={("\001"):split(frame.status.selected)}})
elseif frame.type == "TSMTabGroup" then
tinsert(path, {type="TabGroup", value=frame.localstatus.selected})
end
return private:FramePathHelper(frame, path)
end
function TSM:GetTSMFrameSelectionPath()
if not TSM:TSMFrameIsVisible() then return end
local path = {}
tinsert(path, {type="Icon", value=TSM.Frame.selected.info.name})
private:FramePathHelper(TSM.Frame, path)
return path
end

390
TradeSkillMaster/GUI/ScrollingTable.lua

@ -0,0 +1,390 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local RT_COUNT = 1
local HEAD_HEIGHT = 27
local HEAD_SPACE = 4
local function OnSizeChanged(st, width, height)
width = width - 14
if st.headCols then
-- adjust head col widths
for i, col in ipairs(st.headCols) do
col:SetWidth(col.info.width*width)
end
end
-- calculate new number of rows
st.NUM_ROWS = max(floor((height-st.HEAD_HEIGHT-HEAD_SPACE)/st.ROW_HEIGHT), 0)
-- hide all extra rows and clear their data
for i=st.NUM_ROWS+1, #st.rows do
st.rows[i]:Hide()
st.rows[i].data = nil
end
while #st.rows < st.NUM_ROWS do
st:AddRow()
end
-- adjust rows widths
for _, row in ipairs(st.rows) do
for i, col in ipairs(row.cols) do
if st.headCols then
col:SetWidth(st.headCols[i].info.width*width)
else
col:SetWidth(width)
end
if col.text.fontHeight < 13 then
col.text:SetFont(TSMAPI.Design:GetContentFont(), 13)
col.text.fontHeight = 13
end
end
end
st:RefreshRows()
end
local function GetTableIndex(tbl, value)
for i, v in pairs(tbl) do
if value == v then
return i
end
end
end
local function OnColumnClick(self, button, ...)
if self.st.sortInfo.enabled and button == "LeftButton" then
if self.st.sortInfo.col == self.colNum then
self.st.sortInfo.ascending = not self.st.sortInfo.ascending
else
self.st.sortInfo.col = self.colNum
self.st.sortInfo.ascending = true
end
self.st.updateSort = true
self.st:RefreshRows()
end
if self.st.handlers.OnColumnClick then
self.st.handlers.OnColumnClick(self, button, ...)
end
end
local defaultColScripts = {
OnEnter = function(self, ...)
if not self.row.data then return end
if not self.st.highlightDisabled then
self.row.highlight:Show()
end
local handler = self.st.handlers.OnEnter
if handler then
handler(self.st, self.row.data, self, ...)
end
end,
OnLeave = function(self, ...)
if not self.row.data then return end
if self.st.selectionDisabled or not self.st.selected or self.st.selected ~= GetTableIndex(self.st.rowData, self.row.data) then
self.row.highlight:Hide()
end
local handler = self.st.handlers.OnLeave
if handler then
handler(self.st, self.row.data, self, ...)
end
end,
OnClick = function(self, ...)
if not self.row.data then return end
self.st:ClearSelection()
self.st.selected = GetTableIndex(self.st.rowData, self.row.data)
self.row.highlight:Show()
local handler = self.st.handlers.OnClick
if handler then
handler(self.st, self.row.data, self, ...)
end
end,
OnDoubleClick = function(self, ...)
if not self.row.data then return end
local handler = self.st.handlers.OnDoubleClick
if handler then
handler(self.st, self.row.data, self, ...)
end
end,
}
local methods = {
RefreshRows = function(st)
if not st.rowData then return end
FauxScrollFrame_Update(st.scrollFrame, #st.rowData, st.NUM_ROWS, st.ROW_HEIGHT)
local offset = FauxScrollFrame_GetOffset(st.scrollFrame)
st.offset = offset
-- hide all rows and clear their data
for i=1, st.NUM_ROWS do
st.rows[i]:Hide()
st.rows[i].data = nil
end
-- do sorting if enabled
if st.sortInfo.enabled and st.sortInfo.col and st.updateSort then
local function SortHelper(rowA, rowB)
local sortArgA = rowA.cols[st.sortInfo.col].sortArg
local sortArgB = rowB.cols[st.sortInfo.col].sortArg
if st.sortInfo.ascending then
return sortArgA < sortArgB
else
return sortArgA > sortArgB
end
end
sort(st.rowData, SortHelper)
st.updateSort = nil
end
-- set row data
for i=1, min(st.NUM_ROWS, #st.rowData) do
st.rows[i]:Show()
local data = st.rowData[i+offset]
if not data then break end
st.rows[i].data = data
if (st.selected == GetTableIndex(st.rowData, data) and not st.selectionDisabled) or st.rows[i]:IsMouseOver() or (st.highlighted and st.highlighted == GetTableIndex(st.rowData, data)) then
st.rows[i].highlight:Show()
else
st.rows[i].highlight:Hide()
end
for j, col in ipairs(st.rows[i].cols) do
local colData = data.cols[j]
if type(colData.value) == "function" then
col:SetText(colData.value(unpack(colData.args)))
else
col:SetText(colData.value)
end
end
end
end,
SetData = function(st, rowData)
st.rowData = rowData
st.updateSort = true
st:RefreshRows()
end,
SetSelection = function(st, rowNum)
st.selected = rowNum
st:RefreshRows()
end,
GetSelection = function(st)
return st.selected
end,
ClearSelection = function(st)
st.selected = nil
st:RefreshRows()
end,
DisableSelection = function(st, value)
st.selectionDisabled = value
end,
EnableSorting = function(st, value, defaultCol)
st.sortInfo.enabled = value
st.sortInfo.col = abs(defaultCol or 1)
st.sortInfo.ascending = not defaultCol or defaultCol > 0
st.updateSort = true
st:RefreshRows()
end,
DisableHighlight = function(st, value)
st.highlightDisabled = value
end,
SetScrollOffset = function(st, offset)
local maxOffset = max(#st.rowData - st.NUM_ROWS, 0)
if not offset or offset < 0 or offset > maxOffset then
return -- invalid offset
end
local scrollPercent = offset / maxOffset
local maxPixelOffset = st.scrollFrame:GetVerticalScrollRange() + st.ROW_HEIGHT * 2
local pixelOffset = scrollPercent * maxPixelOffset
FauxScrollFrame_SetOffset(st.scrollFrame, offset)
st.scrollFrame:SetVerticalScroll(pixelOffset)
end,
SetHighlighted = function(st, row)
st.highlighted = row
st:RefreshRows()
end,
AddRow = function(st)
local row = CreateFrame("Frame", nil, st.contentFrame)
row:SetHeight(st.ROW_HEIGHT)
if #st.rows == 0 then
row:SetPoint("TOPLEFT", 0, -(st.HEAD_HEIGHT+HEAD_SPACE))
row:SetPoint("TOPRIGHT", 0, -(st.HEAD_HEIGHT+HEAD_SPACE))
else
row:SetPoint("TOPLEFT", st.rows[#st.rows], "BOTTOMLEFT")
row:SetPoint("TOPRIGHT", st.rows[#st.rows], "BOTTOMRIGHT")
end
local highlight = row:CreateTexture()
highlight:SetAllPoints()
highlight:SetTexture(1, .9, 0, .2)
highlight:Hide()
row.highlight = highlight
row.st = st
row.cols = {}
for j, info in ipairs(st.colInfo or {{}}) do
local col = CreateFrame("Button", nil, row)
local text = col:CreateFontString()
text:SetFont(TSMAPI.Design:GetContentFont(), min(13, st.ROW_HEIGHT))
text:SetJustifyH(info.align or "LEFT")
text:SetJustifyV("CENTER")
text:SetPoint("TOPLEFT", 1, -1)
text:SetPoint("BOTTOMRIGHT", -1, 1)
text.fontHeight = min(13, st.ROW_HEIGHT)
col.text = text
col:SetFontString(text)
col:SetHeight(st.ROW_HEIGHT)
col:RegisterForClicks("AnyUp")
for name, func in pairs(defaultColScripts) do
col:SetScript(name, func)
end
col.st = st
col.row = row
if j == 1 then
col:SetPoint("TOPLEFT")
else
col:SetPoint("TOPLEFT", row.cols[j-1], "TOPRIGHT")
end
tinsert(row.cols, col)
end
tinsert(st.rows, row)
end,
}
function TSMAPI:CreateScrollingTable(parent, colInfo, handlers, headFontSize)
assert(type(parent) == "table", format("Invalid parent argument. Type is %s.", type(parent)))
local rtName = "TSMScrollingTable"..RT_COUNT
RT_COUNT = RT_COUNT + 1
local st = CreateFrame("Frame", rtName, parent)
if not colInfo then
st.HEAD_HEIGHT = -HEAD_SPACE
elseif headFontSize then
st.HEAD_HEIGHT = headFontSize + 4
else
st.HEAD_HEIGHT = HEAD_HEIGHT
end
st.ROW_HEIGHT = 15
st.NUM_ROWS = floor((parent:GetHeight()-st.HEAD_HEIGHT-HEAD_SPACE)/st.ROW_HEIGHT)
st.colInfo = colInfo
st:SetScript("OnSizeChanged", OnSizeChanged)
local contentFrame = CreateFrame("Frame", rtName.."Content", st)
contentFrame:SetPoint("TOPLEFT")
contentFrame:SetPoint("BOTTOMRIGHT", -15, 0)
st.contentFrame = contentFrame
-- frame to hold the header columns and the rows
local scrollFrame = CreateFrame("ScrollFrame", rtName.."ScrollFrame", st, "FauxScrollFrameTemplate")
scrollFrame:SetScript("OnVerticalScroll", function(self, offset)
FauxScrollFrame_OnVerticalScroll(self, offset, st.ROW_HEIGHT, function() st:RefreshRows() end)
end)
scrollFrame:SetAllPoints(contentFrame)
st.scrollFrame = scrollFrame
-- make the scroll bar consistent with the TSM theme
local scrollBar = _G[scrollFrame:GetName().."ScrollBar"]
scrollBar:ClearAllPoints()
scrollBar:SetPoint("BOTTOMRIGHT", st, -1, 1)
scrollBar:SetPoint("TOPRIGHT", st, -1, -st.HEAD_HEIGHT-HEAD_SPACE)
scrollBar:SetWidth(12)
local thumbTex = scrollBar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetContentColor(thumbTex)
thumbTex:SetHeight(50)
thumbTex:SetWidth(scrollBar:GetWidth())
_G[scrollBar:GetName().."ScrollUpButton"]:Hide()
_G[scrollBar:GetName().."ScrollDownButton"]:Hide()
-- create the header columns
if colInfo then
st.headCols = {}
for i, info in ipairs(colInfo) do
local col = CreateFrame("Button", rtName.."HeadCol"..i, st.contentFrame)
col:SetHeight(st.HEAD_HEIGHT)
if i == 1 then
col:SetPoint("TOPLEFT")
else
col:SetPoint("TOPLEFT", st.headCols[i-1], "TOPRIGHT")
end
col.info = info
col.st = st
col.colNum = i
col:RegisterForClicks("AnyUp")
col:SetScript("OnClick", OnColumnClick)
local text = col:CreateFontString()
text:SetJustifyH(info.headAlign or "CENTER")
text:SetJustifyV("CENTER")
if headFontSize then
text:SetFont(TSMAPI.Design:GetContentFont("normal"), headFontSize)
else
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
end
TSMAPI.Design:SetWidgetTextColor(text)
col:SetFontString(text)
col:SetText(info.name or "")
text:SetAllPoints()
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight")
tex:SetTexCoord(0.025, 0.957, 0.087, 0.931)
tex:SetAlpha(0.2)
col:SetHighlightTexture(tex)
tinsert(st.headCols, col)
end
TSMAPI.GUI:CreateHorizontalLine(st, -st.HEAD_HEIGHT)
end
-- set all methods
for name, func in pairs(methods) do
st[name] = func
end
-- create the rows
st.rows = {}
for i=1, st.NUM_ROWS do
st:AddRow()
end
st:SetAllPoints()
st.displayRows = {}
st.handlers = handlers or {}
st.sortInfo = {enabled=nil}
return st
end

120
TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua

@ -0,0 +1,120 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Button.lua
-- This Button widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMButton", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local _G = _G
local PlaySound, CreateFrame, UIParent = PlaySound, CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Button_OnClick(frame, ...)
AceGUI:ClearFocus()
PlaySound("igMainMenuOption")
frame.obj:Fire("OnClick", ...)
end
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetHeight(24)
self:SetWidth(200)
self:SetDisabled(false)
self:SetText()
self.btn:GetFontString():SetTextColor(1, 1, 1, 1)
end,
["SetText"] = function(self, text)
self.btn:SetText(text)
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
if disabled then
self.btn:Disable()
self.btn:GetFontString():SetTextColor(1, 1, 1, 0.5)
else
self.btn:Enable()
self.btn:GetFontString():SetTextColor(1, 1, 1, 1)
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local name = "TSMButton" .. AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
local btn = CreateFrame("Button", name, frame)
btn:Hide()
btn:EnableMouse(true)
btn:SetPoint("TOPLEFT", 0, -2)
btn:SetPoint("BOTTOMRIGHT", 0, 2)
TSMAPI.Design:SetContentColor(btn)
local highlight = btn:CreateTexture(nil, "HIGHLIGHT")
highlight:SetAllPoints()
highlight:SetTexture(1, 1, 1, .2)
highlight:SetBlendMode("BLEND")
btn.highlight = highlight
btn:SetScript("OnClick", Button_OnClick)
btn:SetScript("OnEnter", Control_OnEnter)
btn:SetScript("OnLeave", Control_OnLeave)
btn:Show()
local label = btn:CreateFontString()
label:SetPoint("CENTER")
label:SetHeight(15)
label:SetJustifyH("CENTER")
label:SetJustifyV("CENTER")
label:SetFont(TSMAPI.Design:GetContentFont("normal"))
TSMAPI.Design:SetWidgetTextColor(label)
btn:SetFontString(label)
local widget = {
frame = frame,
btn = btn,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
btn.obj = widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

181
TradeSkillMaster/GUI/TSMWidgets/TSMCheckBox.lua

@ -0,0 +1,181 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua
-- This CheckBox widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMCheckBox", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local select, pairs = select, pairs
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function CheckBox_OnMouseDown(frame)
local self = frame.obj
if not self.disabled then
self.text:SetPoint("LEFT", self.btn, "RIGHT", 1, -1)
end
AceGUI:ClearFocus()
end
local function CheckBox_OnMouseUp(frame, button)
local self = frame.obj
if not self.disabled then
self.text:SetPoint("LEFT", self.btn, "RIGHT", 0, 0)
self:ToggleChecked()
if self.checked then
PlaySound("igMainMenuOptionCheckBoxOn")
else
PlaySound("igMainMenuOptionCheckBoxOff")
end
self:Fire("OnValueChanged", self.checked)
end
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetType()
self:SetValue()
self:SetWidth(200)
self:SetDisabled()
self:SetHeight(24)
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetLabelColor(self.text, disabled)
if disabled then
self.frame:Disable()
local r, g, b = self.btn:GetBackdropColor()
self.btn:SetBackdropColor(r, g, b, .5)
SetDesaturation(self.check, true)
else
self.frame:Enable()
local r, g, b = self.btn:GetBackdropColor()
self.btn:SetBackdropColor(r, g, b, 1)
SetDesaturation(self.check, false)
end
end,
["SetValue"] = function(self,value)
local check = self.check
self.checked = value
SetDesaturation(self.check, false)
if value then
self.check:Show()
else
self.check:Hide()
end
self:SetDisabled(self.disabled)
end,
["GetValue"] = function(self)
return self.checked
end,
["SetType"] = function(self, type)
local check = self.check
if type == "radio" then
self.btn:SetWidth(10)
self.btn:SetHeight(10)
self.btn:SetPoint("TOPLEFT", 7, -7)
self.check:SetPoint("TOPLEFT", -1, 5)
else
self.btn:SetWidth(16)
self.btn:SetHeight(16)
self.btn:SetPoint("TOPLEFT", 4, -4)
self.check:SetPoint("TOPLEFT")
end
end,
["ToggleChecked"] = function(self)
self:SetValue(not self:GetValue())
end,
["SetLabel"] = function(self, label)
self.text:SetText(label)
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Button", nil, UIParent)
frame:Hide()
frame:EnableMouse(true)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
frame:SetScript("OnMouseDown", CheckBox_OnMouseDown)
frame:SetScript("OnMouseUp", CheckBox_OnMouseUp)
local btn = CreateFrame("Button", nil, frame)
btn:EnableMouse(false)
btn:SetWidth(16)
btn:SetHeight(16)
btn:SetPoint("TOPLEFT", 4, -4)
TSMAPI.Design:SetContentColor(btn)
local highlight = btn:CreateTexture(nil, "HIGHLIGHT")
highlight:SetAllPoints()
highlight:SetTexture(1, 1, 1, .2)
highlight:SetBlendMode("BLEND")
local check = btn:CreateTexture(nil, "OVERLAY")
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
check:SetTexCoord(.12, .88, .12, .88)
check:SetBlendMode("BLEND")
check:SetPoint("BOTTOMRIGHT")
local text = frame:CreateFontString(nil, "OVERLAY")
text:SetJustifyH("LEFT")
text:SetHeight(18)
text:SetPoint("LEFT", btn, "RIGHT")
text:SetPoint("RIGHT")
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
local widget = {
btn = btn,
check = check,
text = text,
highlight = highlight,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
frame.obj = widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

187
TradeSkillMaster/GUI/TSMWidgets/TSMColorPicker.lua

@ -0,0 +1,187 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua
-- This ColorPicker widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMColorPicker", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: ShowUIPanel, HideUIPanel, ColorPickerFrame, OpacitySliderFrame
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function ColorCallback(self, r, g, b, a, isAlpha)
if not self.HasAlpha then
a = 1
end
self:SetColor(r, g, b, a)
if ColorPickerFrame:IsVisible() then
--colorpicker is still open
self:Fire("OnValueChanged", r, g, b, a)
else
--colorpicker is closed, color callback is first, ignore it,
--alpha callback is the final call after it closes so confirm now
if isAlpha then
self:Fire("OnValueConfirmed", r, g, b, a)
end
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function ColorSwatch_OnClick(frame)
HideUIPanel(ColorPickerFrame)
local self = frame.obj
if not self.disabled then
ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG")
ColorPickerFrame.func = function()
local r, g, b = ColorPickerFrame:GetColorRGB()
local a = 1 - OpacitySliderFrame:GetValue()
ColorCallback(self, r, g, b, a)
end
ColorPickerFrame.hasOpacity = self.HasAlpha
ColorPickerFrame.opacityFunc = function()
local r, g, b = ColorPickerFrame:GetColorRGB()
local a = 1 - OpacitySliderFrame:GetValue()
ColorCallback(self, r, g, b, a, true)
end
local r, g, b, a = self.r, self.g, self.b, self.a
if self.HasAlpha then
ColorPickerFrame.opacity = 1 - (a or 0)
end
ColorPickerFrame:SetColorRGB(r, g, b)
ColorPickerFrame.cancelFunc = function()
ColorCallback(self, r, g, b, a, true)
end
ShowUIPanel(ColorPickerFrame)
end
AceGUI:ClearFocus()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetHeight(24)
self:SetWidth(200)
self:SetHasAlpha(false)
self:SetColor(0, 0, 0, 1)
self:SetDisabled(nil)
self:SetLabel(nil)
end,
["SetLabel"] = function(self, text)
self.text:SetText(text)
end,
["SetColor"] = function(self, r, g, b, a)
self.r = r
self.g = g
self.b = b
self.a = a or 1
self.colorSwatch:SetVertexColor(r, g, b, a)
end,
["SetHasAlpha"] = function(self, HasAlpha)
self.HasAlpha = HasAlpha
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetLabelColor(self.text, disabled)
if self.disabled then
self.frame:Disable()
else
self.frame:Enable()
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Button", nil, UIParent)
frame:Hide()
frame:EnableMouse(true)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
frame:SetScript("OnClick", ColorSwatch_OnClick)
local colorSwatch = frame:CreateTexture(nil, "OVERLAY")
colorSwatch:SetWidth(19)
colorSwatch:SetHeight(19)
colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch")
colorSwatch:SetPoint("LEFT")
local texture = frame:CreateTexture(nil, "BACKGROUND")
texture:SetWidth(16)
texture:SetHeight(16)
texture:SetTexture(1, 1, 1)
texture:SetPoint("CENTER", colorSwatch)
texture:Show()
local checkers = frame:CreateTexture(nil, "BACKGROUND")
checkers:SetWidth(14)
checkers:SetHeight(14)
checkers:SetTexture("Tileset\\Generic\\Checkers")
checkers:SetTexCoord(.25, 0, 0.5, .25)
checkers:SetDesaturated(true)
checkers:SetVertexColor(1, 1, 1, 0.75)
checkers:SetPoint("CENTER", colorSwatch)
checkers:Show()
local text = frame:CreateFontString(nil,"OVERLAY")
text:SetHeight(24)
text:SetJustifyH("LEFT")
text:SetTextColor(1, 1, 1)
text:SetPoint("LEFT", colorSwatch, "RIGHT", 2, 0)
text:SetPoint("RIGHT")
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
local widget = {
colorSwatch = colorSwatch,
text = text,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

258
TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Items.lua

@ -0,0 +1,258 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Dropdown-Items.lua
-- This Dropdown-Items widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
-- Lua APIs
local select, assert = select, assert
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame = CreateFrame
local ItemBase = {}
do
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function fixlevels(parent,...)
local i = 1
local child = select(i, ...)
while child do
child:SetFrameLevel(parent:GetFrameLevel()+1)
fixlevels(child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Frame_OnEnter(this)
local self = this.obj
if self.useHighlight then
self.highlight:Show()
end
self:Fire("OnEnter")
if self.specialOnEnter then
self.specialOnEnter(self)
end
end
local function Frame_OnLeave(this)
local self = this.obj
self.highlight:Hide()
self:Fire("OnLeave")
if self.specialOnLeave then
self.specialOnLeave(self)
end
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetDisabled()
self.frame:SetToplevel(true)
self.frame:SetFrameStrata("FULLSCREEN_DIALOG")
end,
["OnRelease"] = function(self)
self:SetDisabled()
self.pullout = nil
self.frame:SetParent(nil)
self.frame:ClearAllPoints()
self.frame:Hide()
end,
["SetPullout"] = function(self, pullout)
self.pullout = pullout
self.frame:SetParent(nil)
self.frame:SetParent(pullout.itemFrame)
self.parent = pullout.itemFrame
fixlevels(pullout.itemFrame, pullout.itemFrame:GetChildren())
end,
["SetText"] = function(self, text)
self.text:SetText(text or "")
end,
["GetText"] = function(self)
return self.text:GetText()
end,
["SetPoint"] = function(self, ...)
self.frame:SetPoint(...)
end,
["Show"] = function(self)
self.frame:Show()
end,
["Hide"] = function(self)
self.frame:Hide()
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetTextColor(self.text, disabled)
self.useHighlight = not disabled
end,
["SetOnLeave"] = function(self, func)
self.specialOnLeave = func
end,
["SetOnEnter"] = function(self, func)
self.specialOnEnter = func
end,
}
function ItemBase.Create(type)
local count = AceGUI:GetNextWidgetNum(type)
local frame = CreateFrame("Button", "TSMDropDownItem"..count)
frame:SetHeight(17)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
local text = frame:CreateFontString(nil,"OVERLAY")
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
text:SetJustifyH("LEFT")
text:SetPoint("TOPLEFT",frame,"TOPLEFT",18,0)
text:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-8,0)
local highlight = frame:CreateTexture(nil, "OVERLAY")
highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
highlight:SetBlendMode("ADD")
highlight:SetHeight(14)
highlight:ClearAllPoints()
highlight:SetPoint("RIGHT",frame,"RIGHT",-3,0)
highlight:SetPoint("LEFT",frame,"LEFT",5,0)
highlight:Hide()
local check = frame:CreateTexture("OVERLAY")
check:SetWidth(16)
check:SetHeight(16)
check:SetPoint("LEFT",frame,"LEFT",3,-1)
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
check:Hide()
local sub = frame:CreateTexture("OVERLAY")
sub:SetWidth(16)
sub:SetHeight(16)
sub:SetPoint("RIGHT",frame,"RIGHT",-3,-1)
sub:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow")
sub:Hide()
frame:SetScript("OnEnter", Frame_OnEnter)
frame:SetScript("OnLeave", Frame_OnLeave)
local widget = {
frame = frame,
text = text,
highlight = highlight,
check = check,
sub = sub,
useHighlight = true,
origMethods = methods,
type = type,
}
for method, func in pairs(methods) do
widget[method] = func
end
frame.obj = widget
return widget
end
end
do
local Type, Version = "TSMDropdown-Item-Execute", 2
local function Frame_OnClick(this)
local self = this.obj
if self.disabled then return end
self:Fire("OnClick")
if self.pullout then
self.pullout:Close()
end
end
local function Constructor()
local item = ItemBase.Create(Type)
item.frame:SetScript("OnClick", Frame_OnClick)
return AceGUI:RegisterAsWidget(item)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)
end
do
local Type, Version = "TSMDropdown-Item-Toggle", 2
local function Frame_OnClick(this, button)
local self = this.obj
if self.disabled then return end
self.value = not self.value
if self.value then
PlaySound("igMainMenuOptionCheckBoxOn")
else
PlaySound("igMainMenuOptionCheckBoxOff")
end
self:UpdateToggle()
self:Fire("OnValueChanged", self.value)
end
local methods = {
["UpdateToggle"] = function(self)
if self.value then
self.check:Show()
else
self.check:Hide()
end
end,
["SetValue"] = function(self, value)
self.value = value
self:UpdateToggle()
end,
["GetValue"] = function(self)
return self.value
end,
}
local function Constructor()
local item = ItemBase.Create(Type)
item.frame:SetScript("OnClick", Frame_OnClick)
for method, func in pairs(methods) do
item[method] = func
end
return AceGUI:RegisterAsWidget(item)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)
end

315
TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Pullout.lua

@ -0,0 +1,315 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Dropdown.lua
-- This Dropdown-Pullout widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
local Type, Version = "TSMDropdown-Pullout", 2
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local min, max, floor = math.min, math.max, math.floor
local select, pairs, ipairs, type = select, pairs, ipairs, type
local tsort = table.sort
-- WoW APIs
local PlaySound = PlaySound
local UIParent, CreateFrame = UIParent, CreateFrame
local _G = _G
--[[-----------------------------------------------------------------------------
Globals
-------------------------------------------------------------------------------]]
local sliderBackdrop = {
bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
tile = true, tileSize = 8, edgeSize = 8,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
}
local DEFAULT_WIDTH = 200
local DEFAULT_MAX_HEIGHT = 600
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function fixstrata(strata, parent, ...)
local i = 1
local child = select(i, ...)
parent:SetFrameStrata(strata)
while child do
fixstrata(strata, child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function OnEnter(item)
local self = item.pullout
for k, v in ipairs(self.items) do
if v.CloseMenu and v ~= item then
v:CloseMenu()
end
end
end
-- See the note in Constructor() for each scroll related function
local function OnMouseWheel(this, value)
this.obj:MoveScroll(value)
end
local function OnScrollValueChanged(this, value)
this.obj:SetScroll(value)
end
local function OnSizeChanged(this)
this.obj:FixScroll()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self.frame:SetParent(UIParent)
end,
["OnRelease"] = function(self)
self:Clear()
self.frame:ClearAllPoints()
self.frame:Hide()
end,
["SetScroll"] = function(self, value)
local status = self.scrollStatus
local frame, child = self.scrollFrame, self.itemFrame
local height, viewheight = frame:GetHeight(), child:GetHeight()
local offset
if height > viewheight then
offset = 0
else
offset = floor((viewheight - height) / 1000 * value)
end
child:ClearAllPoints()
child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset)
child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", self.slider:IsShown() and -12 or 0, offset)
status.offset = offset
status.scrollvalue = value
end,
["MoveScroll"] = function(self, value)
local status = self.scrollStatus
local frame, child = self.scrollFrame, self.itemFrame
local height, viewheight = frame:GetHeight(), child:GetHeight()
if height > viewheight then
self.slider:Hide()
else
self.slider:Show()
local diff = height - viewheight
local delta = 1
if value < 0 then
delta = -1
end
self.slider:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000))
end
end,
["FixScroll"] = function(self)
local status = self.scrollStatus
local frame, child = self.scrollFrame, self.itemFrame
local height, viewheight = frame:GetHeight(), child:GetHeight()
local offset = status.offset or 0
if viewheight < height then
self.slider:Hide()
child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, offset)
self.slider:SetValue(0)
else
self.slider:Show()
local value = (offset / (viewheight - height) * 1000)
if value > 1000 then value = 1000 end
self.slider:SetValue(value)
self:SetScroll(value)
if value < 1000 then
child:ClearAllPoints()
child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset)
child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, offset)
status.offset = offset
end
end
end,
["AddItem"] = function(self, item)
self.items[#self.items + 1] = item
local h = #self.items * 16
self.itemFrame:SetHeight(h)
self.frame:SetHeight(min(h + 20, self.maxHeight))
item.frame:SetPoint("LEFT", self.itemFrame, "LEFT")
item.frame:SetPoint("RIGHT", self.itemFrame, "RIGHT")
item:SetPullout(self)
item:SetOnEnter(OnEnter)
end,
["Open"] = function(self, point, relFrame, relPoint, x, y)
local items = self.items
local frame = self.frame
local itemFrame = self.itemFrame
frame:SetPoint(point, relFrame, relPoint, x, y)
local height = 8
for i, item in pairs(items) do
if i == 1 then
item:SetPoint("TOP", itemFrame, "TOP", 0, -2)
else
item:SetPoint("TOP", items[i-1].frame, "BOTTOM", 0, 1)
end
item:Show()
height = height + 16
end
itemFrame:SetHeight(height)
fixstrata("TOOLTIP", frame, frame:GetChildren())
frame:Show()
self:Fire("OnOpen")
end,
["Close"] = function(self)
self.frame:Hide()
self:Fire("OnClose")
end,
["Clear"] = function(self)
local items = self.items
for i, item in pairs(items) do
AceGUI:Release(item)
items[i] = nil
end
end,
["IterateItems"] = function(self)
return ipairs(self.items)
end,
["SetHideOnLeave"] = function(self, val)
self.hideOnLeave = val
end,
["SetMaxHeight"] = function(self, height)
self.maxHeight = height or DEFAULT_MAX_HEIGHT
if self.frame:GetHeight() > height then
self.frame:SetHeight(height)
elseif (self.itemFrame:GetHeight()+20) < height then
self.frame:SetHeight(self.itemFrame:GetHeight()+20) -- see :AddItem
end
end,
["GetRightBorderWidth"] = function(self)
return 6 + (self.slider:IsShown() and 12 or 0)
end,
["GetLeftBorderWidth"] = function(self)
return 6
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local count = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", "TSMPullout"..count, UIParent)
TSMAPI.Design:SetContentColor(frame)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
frame:SetClampedToScreen(true)
frame:SetWidth(DEFAULT_WIDTH)
frame:SetHeight(DEFAULT_MAX_HEIGHT)
local scrollFrame = CreateFrame("ScrollFrame", nil, frame)
local itemFrame = CreateFrame("Frame", nil, scrollFrame)
local slider = CreateFrame("Slider", "TSMPulloutScrollbar"..count, scrollFrame)
slider:SetOrientation("VERTICAL")
slider:SetHitRectInsets(0, 0, -10, 0)
slider:SetBackdrop(sliderBackdrop)
slider:SetWidth(8)
slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical")
slider:SetFrameStrata("FULLSCREEN_DIALOG")
scrollFrame:SetScrollChild(itemFrame)
scrollFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4)
scrollFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -4, 4)
scrollFrame:EnableMouseWheel(true)
scrollFrame:SetScript("OnMouseWheel", OnMouseWheel)
scrollFrame:SetScript("OnSizeChanged", OnSizeChanged)
scrollFrame:SetToplevel(true)
scrollFrame:SetFrameStrata("FULLSCREEN_DIALOG")
itemFrame:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", 0, 0)
itemFrame:SetPoint("TOPRIGHT", scrollFrame, "TOPRIGHT", -12, 0)
itemFrame:SetHeight(400)
itemFrame:SetToplevel(true)
itemFrame:SetFrameStrata("FULLSCREEN_DIALOG")
slider:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", -16, 0)
slider:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", -16, 0)
scrollFrame:Show()
itemFrame:Show()
slider:Hide()
local widget = {
frame = frame,
slider = slider,
scrollFrame = scrollFrame,
itemFrame = itemFrame,
maxHeight = DEFAULT_MAX_HEIGHT,
scrollStatus = {scrollvalue=0},
count = count,
items = {},
type = Type,
}
for method, func in pairs(methods) do
widget[method] = func
end
frame.obj = widget
scrollFrame.obj = widget
itemFrame.obj = widget
slider.obj = widget
slider:SetScript("OnValueChanged", OnScrollValueChanged)
slider:SetMinMaxValues(0, 1000)
slider:SetValueStep(1)
slider:SetValue(0)
widget:FixScroll()
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

413
TradeSkillMaster/GUI/TSMWidgets/TSMDropdown.lua

@ -0,0 +1,413 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Dropdown.lua
-- This Dropdown widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
local Type, Version = "TSMDropdown", 2
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local min, max, floor = math.min, math.max, math.floor
local select, pairs, ipairs, type = select, pairs, ipairs, type
local tsort = table.sort
-- WoW APIs
local PlaySound = PlaySound
local UIParent, CreateFrame = UIParent, CreateFrame
local _G = _G
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function fixlevels(parent,...)
local i = 1
local child = select(i, ...)
while child do
child:SetFrameLevel(parent:GetFrameLevel()+1)
fixlevels(child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
local function fixstrata(strata, parent, ...)
local i = 1
local child = select(i, ...)
parent:SetFrameStrata(strata)
while child do
fixstrata(strata, child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(this)
this.obj:Fire("OnEnter")
end
local function Control_OnLeave(this)
this.obj:Fire("OnLeave")
end
local function Dropdown_OnHide(this)
local self = this.obj
if self.open then
self.pullout:Close()
end
end
local function Dropdown_TogglePullout(this, button)
local self = this.obj
if self.disabled then return end
PlaySound("igMainMenuOptionCheckBoxOn") -- missleading name, but the Blizzard code uses this sound
if self.open then
self.open = nil
self.pullout:Close()
AceGUI:ClearFocus()
else
self.open = true
-- self.pullout:SetWidth(self.dropdown:GetWidth())
self.pullout:SetWidth(self.frame:GetWidth()-8)
self.pullout:Open("TOPLEFT", self.frame, "BOTTOMLEFT", 0, self.label:IsShown() and -2 or 0)
AceGUI:SetFocus(self)
end
end
local function OnPulloutOpen(this)
local self = this.userdata.obj
local value = self.value
if not self.multiselect then
for i, item in this:IterateItems() do
item:SetValue(item.userdata.value == value)
end
end
self.open = true
end
local function OnPulloutClose(this)
local self = this.userdata.obj
self.open = nil
self:Fire("OnClosed")
end
local function ShowMultiText(self)
local text
for i, widget in self.pullout:IterateItems() do
if widget.type == "TSMDropdown-Item-Toggle" then
if widget:GetValue() then
if text then
text = text..", "..widget:GetText()
else
text = widget:GetText()
end
end
end
end
self:SetText(text)
end
local function OnItemValueChanged(this, event, checked)
local self = this.userdata.obj
if self.multiselect then
self:Fire("OnValueChanged", this.userdata.value, checked)
ShowMultiText(self)
else
if checked then
self:SetValue(this.userdata.value)
self:Fire("OnValueChanged", this.userdata.value)
else
this:SetValue(true)
end
if self.open then
self.pullout:Close()
end
end
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
local pullout = AceGUI:Create("TSMDropdown-Pullout")
self.pullout = pullout
pullout.userdata.obj = self
pullout:SetCallback("OnClose", OnPulloutClose)
pullout:SetCallback("OnOpen", OnPulloutOpen)
self.pullout.frame:SetFrameLevel(self.frame:GetFrameLevel() + 1)
fixlevels(self.pullout.frame, self.pullout.frame:GetChildren())
self:SetHeight(44)
self:SetWidth(200)
self:SetLabel()
self:ClearMultiselectChecked()
end,
["OnRelease"] = function(self)
if self.open then
self.pullout:Close()
end
AceGUI:Release(self.pullout)
self.pullout = nil
self:SetText("")
self:SetDisabled(false)
self:SetMultiselect(false)
self.value = nil
self.list = nil
self.open = nil
self.hasClose = nil
self.frame:ClearAllPoints()
self.frame:Hide()
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetTextColor(self.text, disabled)
TSMAPI.Design:SetWidgetLabelColor(self.label, disabled)
if disabled then
self.button:Disable()
else
self.button:Enable()
end
end,
["ClearFocus"] = function(self)
if self.open then
self.pullout:Close()
end
end,
["SetText"] = function(self, text)
self.text:SetText(text or "")
end,
["SetLabel"] = function(self, text)
if text and text ~= "" then
self.label:SetText(text)
self.label:Show()
self.dropdown:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 2, -18)
self:SetHeight(44)
self.alignoffset = 30
else
self.label:SetText("")
self.label:Hide()
self.dropdown:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 2, 0)
self:SetHeight(26)
self.alignoffset = 12
end
end,
["SetValue"] = function(self, value)
if self.list then
self:SetText(self.list[value] or "")
end
self.value = value
end,
["GetValue"] = function(self)
return self.value
end,
["SetItemValue"] = function(self, item, value)
if not self.multiselect then return end
for i, widget in self.pullout:IterateItems() do
if widget.userdata.value == item then
if widget.SetValue then
widget:SetValue(value)
end
end
end
ShowMultiText(self)
end,
["SetItemDisabled"] = function(self, item, disabled)
for i, widget in self.pullout:IterateItems() do
if widget.userdata.value == item then
widget:SetDisabled(disabled)
end
end
end,
["AddListItem"] = function(self, value, text, itemType)
itemType = itemType or "TSMDropdown-Item-Toggle"
local exists = AceGUI:GetWidgetVersion(itemType)
if not exists then error(("The given item type, %q, does not exist within AceGUI-3.0"):format(tostring(itemType)), 2) end
local item = AceGUI:Create(itemType)
item:SetText(text)
item.userdata.obj = self
item.userdata.value = value
item:SetValue()
item:SetCallback("OnValueChanged", OnItemValueChanged)
self.pullout:AddItem(item)
end,
["AddCloseButton"] = function(self)
if not self.hasClose then
local close = AceGUI:Create("TSMDropdown-Item-Execute")
close:SetText(CLOSE)
self.pullout:AddItem(close)
self.hasClose = true
end
end,
["SetList"] = function(self, list, order, itemType)
self.sortlist = self.sortlist or {}
self.list = list
self.pullout:Clear()
self.hasClose = nil
if not list then return end
if type(order) ~= "table" then
for v in pairs(list) do
self.sortlist[#self.sortlist + 1] = v
end
tsort(self.sortlist)
for i, key in ipairs(self.sortlist) do
self:AddListItem(key, list[key], itemType)
self.sortlist[i] = nil
end
else
for i, key in ipairs(order) do
self:AddListItem(key, list[key], itemType)
end
end
if self.multiselect then
ShowMultiText(self)
self:AddCloseButton()
end
end,
["AddItem"] = function(self, value, text, itemType)
if self.list then
self.list[value] = text
self:AddListItem(value, text, itemType)
end
end,
["SetMultiselect"] = function(self, multi)
self.multiselect = multi
if multi then
ShowMultiText(self)
self:AddCloseButton()
end
end,
["GetMultiselect"] = function(self)
return self.multiselect
end,
["ClearMultiselectChecked"] = function(self)
for i, widget in self.pullout:IterateItems() do
if widget.type == "TSMDropdown-Item-Toggle" then
widget:SetValue()
end
end
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local count = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
local dropdown = CreateFrame("Frame", "TSMDropDown"..count, frame, "UIDropDownMenuTemplate")
frame:SetScript("OnHide", Dropdown_OnHide)
dropdown:ClearAllPoints()
dropdown:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -7, 0)
dropdown:SetScript("OnHide", nil)
dropdown:SetScript("OnEnter", Control_OnEnter)
dropdown:SetScript("OnLeave", Control_OnLeave)
dropdown:SetScript("OnMouseUp", function(self, button) Dropdown_TogglePullout(self.obj.button, button) end)
TSMAPI.Design:SetContentColor(dropdown)
local left = _G[dropdown:GetName().."Left"]
local middle = _G[dropdown:GetName().."Middle"]
local right = _G[dropdown:GetName().."Right"]
middle:ClearAllPoints()
right:ClearAllPoints()
middle:SetPoint("LEFT", left, "RIGHT", 0, 0)
middle:SetPoint("RIGHT", right, "LEFT", 0, 0)
right:SetPoint("TOPRIGHT", dropdown, "TOPRIGHT", 0, 17)
local button = _G[dropdown:GetName().."Button"]
button:RegisterForClicks("AnyUp")
button:SetScript("OnEnter", Control_OnEnter)
button:SetScript("OnLeave", Control_OnLeave)
button:SetScript("OnClick", Dropdown_TogglePullout)
button:ClearAllPoints()
button:SetPoint("RIGHT", dropdown, 0, 0)
local text = _G[dropdown:GetName().."Text"]
text:ClearAllPoints()
text:SetPoint("RIGHT", button, "LEFT", -2, 0)
text:SetPoint("LEFT", dropdown, "LEFT", 8, 0)
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
text:SetShadowColor(0, 0, 0, 0)
local label = frame:CreateFontString(nil, "OVERLAY")
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
label:SetJustifyH("LEFT")
label:SetHeight(18)
label:SetFont(TSMAPI.Design:GetContentFont("small"))
label:SetShadowColor(0, 0, 0, 0)
label:Hide()
left:Hide()
middle:Hide()
right:Hide()
local widget = {
frame = frame,
label = label,
dropdown = dropdown,
text = text,
button = button,
count = count,
alignoffset = 30,
type = Type,
}
for method, func in pairs(methods) do
widget[method] = func
end
frame.obj = widget
dropdown.obj = widget
text.obj = widget
button.obj = widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

283
TradeSkillMaster/GUI/TSMWidgets/TSMEditBox.lua

@ -0,0 +1,283 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua
-- This EditBox widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMEditBox", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local tostring, pairs = tostring, pairs
-- WoW APIs
local PlaySound = PlaySound
local GetCursorInfo, ClearCursor, GetSpellInfo = GetCursorInfo, ClearCursor, GetSpellInfo
local CreateFrame, UIParent = CreateFrame, UIParent
local _G = _G
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
if not TSMEditBoxInsertLink then
-- upgradeable hook
hooksecurefunc("ChatEdit_InsertLink", function(...) return _G.TSMEditBoxInsertLink(...) end)
end
function _G.TSMEditBoxInsertLink(text)
for i = 1, AceGUI:GetWidgetCount(Type) do
local editbox = _G["TSMEditBox"..i]
if editbox and editbox:IsVisible() and editbox:HasFocus() then
editbox:Insert(text)
return true
end
end
end
local function ShowButton(self)
if not self.disablebutton then
self.button:Show()
self.editbox:SetTextInsets(0, 20, 3, 3)
end
end
local function HideButton(self)
self.button:Hide()
self.editbox:SetTextInsets(0, 0, 3, 3)
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function Frame_OnShowFocus(frame)
frame.obj.editbox:SetFocus()
frame:SetScript("OnShow", nil)
end
local function EditBox_OnEscapePressed(frame)
AceGUI:ClearFocus()
end
local function EditBox_OnEnterPressed(frame)
local self = frame.obj
local value = frame:GetText()
self:ClearFocus()
local cancel = self:Fire("OnEnterPressed", value)
if not cancel then
PlaySound("igMainMenuOptionCheckBoxOn")
HideButton(self)
end
end
local function EditBox_OnReceiveDrag(frame)
local self = frame.obj
local type, id, info = GetCursorInfo()
if type == "item" then
self:SetText(info)
self:Fire("OnEnterPressed", info)
ClearCursor()
elseif type == "spell" then
local name = GetSpellInfo(id, info)
self:SetText(name)
self:Fire("OnEnterPressed", name)
ClearCursor()
end
HideButton(self)
AceGUI:ClearFocus()
end
local function EditBox_OnTextChanged(frame)
local self = frame.obj
local value = frame:GetText()
if tostring(value) ~= tostring(self.lasttext) then
self:Fire("OnTextChanged", value)
self.lasttext = value
ShowButton(self)
end
end
local function EditBox_OnFocusGained(frame)
AceGUI:SetFocus(frame.obj)
frame.obj:Fire("OnEditFocusGained")
end
local function EditBox_OnFocusLost(frame)
frame.obj:Fire("OnEditFocusLost")
end
local function Button_OnClick(frame)
local editbox = frame.obj.editbox
editbox:ClearFocus()
EditBox_OnEnterPressed(editbox)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- height is controlled by SetLabel
self:SetWidth(200)
self:SetDisabled()
self:SetLabel()
self:SetText()
self:DisableButton()
self:SetMaxLetters(0)
TSMAPI.GUI:SetAutoComplete(self.editbox, nil)
end,
["OnRelease"] = function(self)
self:ClearFocus()
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetTextColor(self.editbox, disabled)
self.editbox:EnableMouse(not disabled)
TSMAPI.Design:SetWidgetLabelColor(self.label, disabled)
if disabled then
self.editbox:ClearFocus()
end
end,
["SetText"] = function(self, text)
if self.disabled and text then
text = gsub(text, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])", "")
text = gsub(text, "|r", "")
end
self.lasttext = text or ""
self.editbox:SetText(text or "")
self.editbox:SetCursorPosition(0)
HideButton(self)
end,
["GetText"] = function(self, text)
return self.editbox:GetText()
end,
["SetLabel"] = function(self, text)
if text and text ~= "" then
self.label:SetText(text)
self.label:Show()
self.editbox:SetPoint("TOPLEFT", 2, -18)
self:SetHeight(44)
self.alignoffset = 30
else
self.label:SetText("")
self.label:Hide()
self.editbox:SetPoint("TOPLEFT", 2, 0)
self:SetHeight(26)
self.alignoffset = 12
end
end,
["DisableButton"] = function(self, disabled)
self.disablebutton = disabled
if disabled then
HideButton(self)
end
end,
["SetMaxLetters"] = function (self, num)
self.editbox:SetMaxLetters(num or 0)
end,
["ClearFocus"] = function(self)
self.editbox:ClearFocus()
self.frame:SetScript("OnShow", nil)
end,
["SetFocus"] = function(self)
self.editbox:SetFocus()
if not self.frame:IsShown() then
self.frame:SetScript("OnShow", Frame_OnShowFocus)
end
end,
["SetAutoComplete"] = function(self, params)
TSMAPI.GUI:SetAutoComplete(self.editbox, params)
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
frame:Hide()
local editbox = CreateFrame("EditBox", "TSMEditBox"..num, frame)
editbox:SetAutoFocus(false)
editbox:SetScript("OnEnter", Control_OnEnter)
editbox:SetScript("OnLeave", Control_OnLeave)
editbox:SetScript("OnEscapePressed", EditBox_OnEscapePressed)
editbox:SetScript("OnEnterPressed", EditBox_OnEnterPressed)
editbox:SetScript("OnTextChanged", EditBox_OnTextChanged)
editbox:SetScript("OnReceiveDrag", EditBox_OnReceiveDrag)
editbox:SetScript("OnMouseDown", EditBox_OnReceiveDrag)
editbox:SetScript("OnEditFocusGained", EditBox_OnFocusGained)
editbox:SetScript("OnEditFocusLost", EditBox_OnFocusLost)
editbox:SetTextInsets(0, 0, 3, 3)
editbox:SetMaxLetters(256)
editbox:SetPoint("BOTTOMRIGHT", -6, 0)
editbox:SetHeight(19)
editbox:SetFont(TSMAPI.Design:GetContentFont("normal"))
editbox:SetShadowColor(0, 0, 0, 0)
TSMAPI.Design:SetContentColor(editbox)
local label = frame:CreateFontString(nil, "OVERLAY")
label:SetPoint("TOPLEFT", 0, -2)
label:SetPoint("TOPRIGHT", 0, -2)
label:SetJustifyH("LEFT")
label:SetJustifyV("CENTER")
label:SetHeight(18)
label:SetFont(TSMAPI.Design:GetContentFont("normal"))
label:SetShadowColor(0, 0, 0, 0)
local button = CreateFrame("Button", nil, editbox, "UIPanelButtonTemplate")
button:SetWidth(40)
button:SetHeight(20)
button:SetPoint("RIGHT", -2, 0)
button:SetText(OKAY)
button:SetScript("OnClick", Button_OnClick)
button:Hide()
local widget = {
frame = frame,
alignoffset = 30,
editbox = editbox,
label = label,
button = button,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
editbox.obj, button.obj = widget, widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

231
TradeSkillMaster/GUI/TSMWidgets/TSMGroupBox.lua

@ -0,0 +1,231 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua
-- This EditBox widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMGroupBox", 2
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local tostring, pairs = tostring, pairs
-- WoW APIs
local PlaySound = PlaySound
local GetCursorInfo, ClearCursor, GetSpellInfo = GetCursorInfo, ClearCursor, GetSpellInfo
local CreateFrame, UIParent = CreateFrame, UIParent
local _G = _G
-- local variables
local groupSelectionFrame
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function CreateGroupSelectionFrame()
if groupSelectionFrame then return end
groupSelectionFrame = CreateFrame("Frame", nil, TSMMainFrame1)
TSMAPI.Design:SetFrameBackdropColor(groupSelectionFrame)
groupSelectionFrame:SetWidth(300)
groupSelectionFrame:SetHeight(400)
groupSelectionFrame:SetPoint("CENTER")
local label = TSMAPI.GUI:CreateLabel(groupSelectionFrame)
label:SetPoint("TOPLEFT", 5, -2)
label:SetPoint("TOPRIGHT", -5, -2)
label:SetHeight(40)
label:SetJustifyV("CENTER")
label:SetJustifyH("CENTER")
label:SetText(L["Select a group from the list below and click 'OK' at the bottom."])
local container = CreateFrame("Frame", nil, groupSelectionFrame)
container:SetPoint("TOPLEFT", 5, -45)
container:SetPoint("BOTTOMRIGHT", -5, 45)
TSMAPI.Design:SetFrameColor(container)
groupSelectionFrame.groupTree = TSMAPI:CreateGroupTree(container, nil, nil, true)
local function OnBtnClick(btn)
if btn.which == "clear" then
groupSelectionFrame.groupTree:ClearSelection()
elseif btn.which == "cancel" then
groupSelectionFrame:Hide()
elseif btn.which == "okay" then
local groupBox = groupSelectionFrame.obj
local groupPath = groupSelectionFrame.groupTree:GetGroupBoxSelection()
groupBox:SetText(TSMAPI:FormatGroupPath(groupPath))
groupBox:Fire("OnValueChanged", groupPath)
groupSelectionFrame:Hide()
end
end
local btn = TSMAPI.GUI:CreateButton(groupSelectionFrame, 14)
btn:SetPoint("BOTTOMLEFT", 5, 5)
btn:SetWidth(90)
btn:SetHeight(24)
btn:SetText(L["Clear"])
btn:SetScript("OnClick", OnBtnClick)
btn.which = "clear"
groupSelectionFrame.clearBtn = btn
local btn = TSMAPI.GUI:CreateButton(groupSelectionFrame, 14)
btn:SetPoint("BOTTOMLEFT", groupSelectionFrame.clearBtn, "BOTTOMRIGHT", 5, 0)
btn:SetWidth(90)
btn:SetHeight(24)
btn:SetText(CANCEL)
btn:SetScript("OnClick", OnBtnClick)
btn.which = "cancel"
groupSelectionFrame.cancelBtn = btn
local btn = TSMAPI.GUI:CreateButton(groupSelectionFrame, 14)
btn:SetPoint("BOTTOMLEFT", groupSelectionFrame.cancelBtn, "BOTTOMRIGHT", 5, 0)
btn:SetPoint("BOTTOMRIGHT", -5, 5)
btn:SetWidth(90)
btn:SetHeight(24)
btn:SetText(OKAY)
btn:SetScript("OnClick", OnBtnClick)
btn.which = "okay"
groupSelectionFrame.okBtn = btn
groupSelectionFrame:Hide()
end
local function ShowGroupSelectionFrame(parent, obj)
groupSelectionFrame:SetFrameStrata("FULLSCREEN_DIALOG")
groupSelectionFrame:Show()
groupSelectionFrame:SetPoint("CENTER", parent)
groupSelectionFrame.obj = obj
groupSelectionFrame.groupTree:SetGropBoxSelection(obj:GetText())
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function Control_OnClick(frame)
frame.obj.editbox:ClearFocus()
ShowGroupSelectionFrame(frame, frame.obj)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- height is controlled by SetLabel
self:SetWidth(200)
self:SetDisabled()
self:SetLabel()
self:SetText()
self:SetMaxLetters(0)
CreateGroupSelectionFrame()
end,
["OnRelease"] = function(self)
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetTextColor(self.editbox, disabled)
self.editbox:EnableMouse(not disabled)
TSMAPI.Design:SetWidgetLabelColor(self.label, disabled)
end,
["SetText"] = function(self, text)
self.editbox:SetText(text or "")
self.editbox:SetCursorPosition(0)
end,
["GetText"] = function(self, text)
return self.editbox:GetText()
end,
["SetLabel"] = function(self, text)
if text and text ~= "" then
self.label:SetText(text)
self.label:Show()
self.editbox:SetPoint("TOPLEFT", 2, -18)
self:SetHeight(44)
self.alignoffset = 30
else
self.label:SetText("")
self.label:Hide()
self.editbox:SetPoint("TOPLEFT", 2, 0)
self:SetHeight(26)
self.alignoffset = 12
end
end,
["SetMaxLetters"] = function (self, num)
self.editbox:SetMaxLetters(num or 0)
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
frame:Hide()
local editbox = CreateFrame("EditBox", "TSMGroupBox"..num, frame)
editbox:SetAutoFocus(false)
editbox:SetScript("OnEnter", Control_OnEnter)
editbox:SetScript("OnLeave", Control_OnLeave)
editbox:SetScript("OnEditFocusGained", Control_OnClick)
editbox:SetTextInsets(0, 0, 3, 3)
editbox:SetMaxLetters(256)
editbox:SetPoint("BOTTOMRIGHT", -6, 0)
editbox:SetHeight(19)
editbox:SetFont(TSMAPI.Design:GetContentFont("small"))
editbox:SetShadowColor(0, 0, 0, 0)
TSMAPI.Design:SetContentColor(editbox)
local label = frame:CreateFontString(nil, "OVERLAY")
label:SetPoint("TOPLEFT", 0, -2)
label:SetPoint("TOPRIGHT", 0, -2)
label:SetJustifyH("LEFT")
label:SetJustifyV("CENTER")
label:SetHeight(18)
label:SetFont(TSMAPI.Design:GetContentFont("normal"))
label:SetShadowColor(0, 0, 0, 0)
local widget = {
frame = frame,
alignoffset = 30,
editbox = editbox,
label = label,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
frame.obj = widget
editbox.obj = widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

515
TradeSkillMaster/GUI/TSMWidgets/TSMGroupItemList.lua

@ -0,0 +1,515 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--[[-----------------------------------------------------------------------------
Group Item List Widget
Provides two scroll lists with buttons to move selected items from one list to the other.
-------------------------------------------------------------------------------]]
local TSM = select(2, ...)
local Type, Version = "TSMGroupItemList", 1
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
local ROW_HEIGHT = 16
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function ShowIcon(row)
row.iconFrame:Show()
row.label:SetPoint("TOPLEFT", 20, 0)
row.label:SetPoint("BOTTOMRIGHT")
end
local function HideIcon(row)
row.iconFrame:Hide()
row.label:SetPoint("TOPLEFT", 0, 0)
row.label:SetPoint("BOTTOMRIGHT")
end
local function UpdateScrollFrame(self)
local parent = self:GetParent()
if not parent.obj.GetListCallback then return end
parent.items = parent.obj.GetListCallback(parent == parent.obj.leftFrame and "left" or "right")
if not parent.list then
parent.list = {}
local usedItems = {}
for _, itemLink in ipairs(parent.items) do
local itemString = TSMAPI:GetItemString(itemLink)
local name, link, _, _, _, _, _, _, _, texture = TSMAPI:GetSafeItemInfo(itemString)
if itemString and name and texture and not usedItems[itemString] then
usedItems[itemString] = true
tinsert(parent.list, {value=itemString, link=link, icon=texture, sortText=strlower(name)})
end
end
sort(parent.list, function(a, b) return a.sortText < b.sortText end)
end
local rows = self.rows
-- clear all the rows
for _, v in pairs(rows) do
v.value = nil
v.label:SetText("")
v.iconFrame.icon:SetTexture("")
v:Hide()
end
local rowData = {}
for _, data in ipairs(self:GetParent().list) do
if not data.filtered then
tinsert(rowData, data)
end
end
local maxRows = floor((self.height-5)/(ROW_HEIGHT+2))
FauxScrollFrame_Update(self, #(rowData), maxRows-1, ROW_HEIGHT)
local offset = FauxScrollFrame_GetOffset(self)
local displayIndex = 0
-- make the rows bigger if the scroller isn't showing
if self:IsVisible() then
rows[1]:SetPoint("TOPRIGHT", self:GetParent(), -26, 0)
else
rows[1]:SetPoint("TOPRIGHT", self:GetParent(), -10, 0)
end
for index, data in ipairs(rowData) do
if index >= offset and displayIndex < maxRows then
displayIndex = displayIndex + 1
local row = rows[displayIndex]
row.label:SetText(data.link)
row.value = data.value
row.data = data
if data.selected then
row:LockHighlight()
else
row:UnlockHighlight()
end
if data.icon then
row.iconFrame.icon:SetTexture(data.icon)
ShowIcon(row)
else
HideIcon(row)
end
row:Show()
end
end
end
local function UpdateRows(parent)
local numRows = floor((parent.height-5)/(ROW_HEIGHT+2))
parent.rows = parent.rows or {}
for i=1, numRows do
if not parent.rows[i] then
local row = CreateFrame("Button", parent:GetName().."Row"..i, parent:GetParent())
row:SetHeight(ROW_HEIGHT)
row:SetScript("OnClick", function(self)
self.data.selected = not self.data.selected
if self.data.selected then
self:LockHighlight()
else
self:UnlockHighlight()
end
end)
row:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_NONE")
GameTooltip:SetPoint("LEFT", parent:GetParent():GetParent(), "RIGHT")
TSMAPI:SafeTooltipLink(self.data.link)
GameTooltip:Show()
end)
-- row:SetScript("OnLeave", function() GameTooltip:Hide() BattlePetTooltip:Hide() end)
row:SetScript("OnLeave", function() GameTooltip:Hide() end)
if i > 1 then
row:SetPoint("TOPLEFT", parent.rows[i-1], "BOTTOMLEFT", 0, -2)
row:SetPoint("TOPRIGHT", parent.rows[i-1], "BOTTOMRIGHT", 0, -2)
else
row:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
row:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 4, 0)
end
-- highlight / selection texture for the row
local highlightTex = row:CreateTexture()
highlightTex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight")
highlightTex:SetPoint("TOPRIGHT", row, "TOPRIGHT", 0, 0)
highlightTex:SetPoint("BOTTOMLEFT")
highlightTex:SetAlpha(0.7)
row:SetHighlightTexture(highlightTex)
-- icon that goes to the left of the text
local iconFrame = CreateFrame("Frame", nil, row)
iconFrame:SetHeight(ROW_HEIGHT-2)
iconFrame:SetWidth(ROW_HEIGHT-2)
iconFrame:SetPoint("TOPLEFT")
row.iconFrame = iconFrame
-- texture that goes inside the iconFrame
local iconTexture = iconFrame:CreateTexture(nil, "BACKGROUND")
iconTexture:SetAllPoints(iconFrame)
iconTexture:SetVertexColor(1, 1, 1)
iconFrame.icon = iconTexture
local label = row:CreateFontString(nil, "OVERLAY")
label:SetFont(TSMAPI.Design:GetContentFont("normal"))
label:SetJustifyH("LEFT")
label:SetJustifyV("CENTER")
label:SetPoint("TOPLEFT", 20, 0)
label:SetPoint("BOTTOMRIGHT", 10, 0)
TSMAPI.Design:SetWidgetTextColor(label)
row.label = label
parent.rows[i] = row
end
end
UpdateScrollFrame(parent)
end
local function OnButtonClick(self)
local selected = {}
local rows, rowData
if self.type == "Add" then
rows = self.obj.leftFrame.scrollFrame.rows
rowData = self.obj.leftFrame.list
elseif self.type == "Remove" then
rows = self.obj.rightFrame.scrollFrame.rows
rowData = self.obj.rightFrame.list
end
if not rows then error("Invalid type") end
local temp = {}
for _, row in pairs(rows) do
if row.data and row.data.selected and row.value then
row.data.selected = false
row:UnlockHighlight()
temp[row.value] = true
tinsert(selected, row.value)
end
end
for _, data in pairs(rowData) do
if data.selected and data.value and not temp[data.value] then
data.selected = false
tinsert(selected, data.value)
end
end
self.obj:Fire("On"..self.type.."Clicked", selected)
end
local function OnFilterSet(self)
self:ClearFocus()
local text = strlower(TSMAPI:StrEscape(self:GetText():trim()))
local filterStr, minLevel, maxLevel, minILevel, maxILevel
for _, part in ipairs({("/"):split(text)}) do
part = part:trim()
if part ~= "" then
local lvl = tonumber(part)
local ilvl = gsub(part, "^i", "")
ilvl = tonumber(ilvl)
if lvl then
if not minLevel then
minLevel = lvl
elseif not maxLevel then
maxLevel = lvl
else
return TSM:Print(L["Invalid filter."])
end
elseif ilvl then
if not minILevel then
minILevel = ilvl
elseif not maxILevel then
maxILevel = ilvl
else
return TSM:Print(L["Invalid filter."])
end
else
if filterStr then
return TSM:Print(L["Invalid filter."])
end
filterStr = part
end
end
end
filterStr = filterStr or ""
minLevel = minLevel or 0
maxLevel = maxLevel or math.huge
minILevel = minILevel or 0
maxILevel = maxILevel or math.huge
for _, info in ipairs(self.obj.leftFrame.list) do
local name, _, _, ilvl, lvl = TSMAPI:GetSafeItemInfo(info.link)
local selected = (strfind(strlower(name), filterStr) and ilvl >= minILevel and ilvl <= maxILevel and lvl >= minLevel and lvl <= maxLevel)
info.selected = selected
info.filtered = not selected
end
for _, info in ipairs(self.obj.rightFrame.list) do
local name, _, _, ilvl, lvl = TSMAPI:GetSafeItemInfo(info.link)
local selected = (strfind(strlower(name), filterStr) and ilvl >= minILevel and ilvl <= maxILevel and lvl >= minLevel and lvl <= maxLevel)
info.selected = selected
info.filtered = not selected
end
FauxScrollFrame_SetOffset(self.obj.leftFrame.scrollFrame, 0)
FauxScrollFrame_SetOffset(self.obj.rightFrame.scrollFrame, 0)
UpdateScrollFrame(self.obj.leftFrame.scrollFrame)
UpdateScrollFrame(self.obj.rightFrame.scrollFrame)
end
local function OnClearButtonClicked(self)
for _, info in ipairs(self.obj.leftFrame.list) do
info.selected = false
end
for _, info in ipairs(self.obj.rightFrame.list) do
info.selected = false
end
UpdateScrollFrame(self.obj.leftFrame.scrollFrame)
UpdateScrollFrame(self.obj.rightFrame.scrollFrame)
end
local function OnIgnoreChanged(self, _, value)
TSM.db.global.ignoreRandomEnchants = value
self.obj.leftFrame.list = nil
UpdateScrollFrame(self.obj.leftFrame.scrollFrame)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- restore default values
self:SetHeight(550)
TSMAPI:CreateTimeDelay(0.05, function() self.parent:DoLayout() end)
self.filter:SetText("")
self.ignoreCheckBox:SetValue(TSM.db.global.ignoreRandomEnchants)
end,
["OnRelease"] = function(self)
-- clear any points / other values
wipe(self.leftFrame.list)
wipe(self.rightFrame.list)
self.frame.leftTitle:SetText("")
self.frame.rightTitle:SetText("")
end,
["OnHeightSet"] = function(self, height)
if height == 100 then return end
self.leftScrollFrame.height = self.frame:GetHeight() - 85
self.rightScrollFrame.height = self.frame:GetHeight() - 85
UpdateRows(self.leftScrollFrame)
UpdateRows(self.rightScrollFrame)
end,
["SetListCallback"] = function(self, callback)
self.GetListCallback = callback
self.leftFrame.list = nil
self.rightFrame.list = nil
UpdateScrollFrame(self.leftScrollFrame)
UpdateScrollFrame(self.rightScrollFrame)
end,
["SetTitle"] = function(self, side, title)
if strlower(side) == "left" then
self.frame.leftTitle:SetText(title)
elseif strlower(side) == "right" then
self.frame.rightTitle:SetText(title)
elseif title then
error("Invalid side passed. Expected 'left' or 'right'")
end
end,
["SetIgnoreVisible"] = function(self, shown)
if shown then
self.ignoreCheckBox.frame:Show()
else
self.ignoreCheckBox.frame:Hide()
end
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local borderColor = TSM.db.profile.frameBackdropColor
local name = "TSMGroupItemList" .. AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", name, UIParent)
frame:Hide()
local leftFrame = CreateFrame("Frame", name.."LeftFrame", frame)
leftFrame:SetPoint("TOPLEFT", 0, -80)
leftFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -7, 0)
TSMAPI.Design:SetContentColor(leftFrame)
leftFrame.list = {}
frame.leftFrame = leftFrame
local leftTitle = frame:CreateFontString(nil, "OVERLAY")
leftTitle:SetFont(TSMAPI.Design:GetContentFont("normal"))
TSMAPI.Design:SetTitleTextColor(leftTitle)
leftTitle:SetJustifyH("LEFT")
leftTitle:SetJustifyV("BOTTOM")
leftTitle:SetHeight(15)
leftTitle:SetPoint("BOTTOMLEFT", leftFrame, "TOPLEFT", 8, 0)
leftTitle:SetPoint("BOTTOMRIGHT", leftFrame, "TOPRIGHT", -8, 0)
frame.leftTitle = leftTitle
local leftSF = CreateFrame("ScrollFrame", name.."LeftFrameScrollFrame", leftFrame, "FauxScrollFrameTemplate")
leftSF:SetPoint("TOPLEFT", 5, -5)
leftSF:SetPoint("BOTTOMRIGHT", -5, 5)
leftSF:SetScript("OnVerticalScroll", function(self, offset)
FauxScrollFrame_OnVerticalScroll(self, offset, ROW_HEIGHT, function() UpdateScrollFrame(self) end)
end)
leftFrame.scrollFrame = leftSF
local leftScrollBar = _G[leftSF:GetName().."ScrollBar"]
leftScrollBar:ClearAllPoints()
leftScrollBar:SetPoint("BOTTOMRIGHT")
leftScrollBar:SetPoint("TOPRIGHT")
leftScrollBar:SetWidth(12)
local thumbTex = leftScrollBar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetFrameColor(thumbTex)
thumbTex:SetHeight(150)
thumbTex:SetWidth(leftScrollBar:GetWidth())
_G[leftScrollBar:GetName().."ScrollUpButton"]:Hide()
_G[leftScrollBar:GetName().."ScrollDownButton"]:Hide()
local rightFrame = CreateFrame("Frame", name.."RightFrame", frame)
rightFrame:SetPoint("TOPLEFT", frame, "TOP", 7, -80)
rightFrame:SetPoint("BOTTOMRIGHT", 0, 0)
TSMAPI.Design:SetContentColor(rightFrame)
rightFrame.list = {}
frame.rightFrame = rightFrame
local rightTitle = frame:CreateFontString(nil, "OVERLAY")
rightTitle:SetFont(TSMAPI.Design:GetContentFont("normal"))
TSMAPI.Design:SetTitleTextColor(rightTitle)
rightTitle:SetJustifyH("LEFT")
rightTitle:SetJustifyV("BOTTOM")
rightTitle:SetHeight(15)
rightTitle:SetPoint("BOTTOMLEFT", rightFrame, "TOPLEFT", 8, 0)
rightTitle:SetPoint("BOTTOMRIGHT", rightFrame, "TOPRIGHT", -8, 0)
frame.rightTitle = rightTitle
local rightSF = CreateFrame("ScrollFrame", name.."RightFrameScrollFrame", rightFrame, "FauxScrollFrameTemplate")
rightSF:SetPoint("TOPLEFT", 5, -5)
rightSF:SetPoint("BOTTOMRIGHT", -5, 5)
rightSF:SetScript("OnVerticalScroll", function(self, offset)
FauxScrollFrame_OnVerticalScroll(self, offset, ROW_HEIGHT, function() UpdateScrollFrame(self) end)
end)
rightFrame.scrollFrame = rightSF
local rightScrollBar = _G[rightSF:GetName().."ScrollBar"]
rightScrollBar:ClearAllPoints()
rightScrollBar:SetPoint("BOTTOMRIGHT")
rightScrollBar:SetPoint("TOPRIGHT")
rightScrollBar:SetWidth(12)
local thumbTex = rightScrollBar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetFrameColor(thumbTex)
thumbTex:SetHeight(150)
thumbTex:SetWidth(rightScrollBar:GetWidth())
_G[rightScrollBar:GetName().."ScrollUpButton"]:Hide()
_G[rightScrollBar:GetName().."ScrollDownButton"]:Hide()
local label = TSMAPI.GUI:CreateLabel(frame, "normal")
label:SetText("Filter:")
label:SetPoint("TOPLEFT", 0, -5)
label:SetHeight(20)
label:SetJustifyV("CENTER")
local filter = TSMAPI.GUI:CreateInputBox(frame)
filter:SetPoint("BOTTOMLEFT", label, "BOTTOMRIGHT", 2, 0)
filter:SetHeight(20)
filter:SetWidth(150)
filter:SetScript("OnEnterPressed", OnFilterSet)
filter.tooltip = L["All items with names containing the specified filter will be selected. This makes it easier to add/remove multiple items at a time."]
local line = TSMAPI.GUI:CreateHorizontalLine(frame, 0)
line:SetPoint("TOPLEFT", 0, -58)
line:SetPoint("TOPRIGHT", 0, -58)
local line = TSMAPI.GUI:CreateVerticalLine(frame, 0)
line:ClearAllPoints()
line:SetPoint("TOP", 0, -60)
line:SetPoint("BOTTOM")
local ignoreCheckBox = TSMAPI.GUI:CreateCheckBox(frame, L["When checked, random enchants will be ignored for ungrouped items.\n\nNB: This will not affect parent group items that were already added with random enchants\n\nIf you have this checked when adding an ungrouped randomly enchanted item, it will act as all possible random enchants of that item."])
ignoreCheckBox:SetLabel(L["Ignore Random Enchants on Ungrouped Items"])
ignoreCheckBox:SetPoint("BOTTOMLEFT", filter, "BOTTOMRIGHT", 20, 5)
ignoreCheckBox:SetPoint("TOPRIGHT", 0, -2)
ignoreCheckBox:SetCallback("OnValueChanged", OnIgnoreChanged)
local addBtn = TSMAPI.GUI:CreateButton(frame, 18)
addBtn:SetPoint("TOPLEFT", 0, -33)
addBtn:SetWidth(170)
addBtn:SetHeight(20)
addBtn:SetText(L["Add >>>"])
addBtn.type = "Add"
addBtn:SetScript("OnClick", OnButtonClick)
local removeBtn = TSMAPI.GUI:CreateButton(frame, 18)
removeBtn:SetPoint("TOPRIGHT", 0, -33)
removeBtn:SetWidth(170)
removeBtn:SetHeight(20)
removeBtn:SetText(L["<<< Remove"])
removeBtn.type = "Remove"
removeBtn:SetScript("OnClick", OnButtonClick)
removeBtn.tooltip = L["You can hold shift while clicking this button to remove the items from ALL groups rather than keeping them in the parent group (if one exists)."]
local clearBtn = TSMAPI.GUI:CreateButton(frame, 16)
clearBtn:SetPoint("BOTTOMLEFT", addBtn, "BOTTOMRIGHT", 15, 0)
clearBtn:SetPoint("BOTTOMRIGHT", removeBtn, "BOTTOMLEFT", -15, 0)
clearBtn:SetHeight(20)
clearBtn:SetText(L["Clear Selection"])
clearBtn:SetScript("OnClick", OnClearButtonClicked)
clearBtn.tooltip = L["Deselects all items in both columns."]
local widget = {
leftFrame = leftFrame,
leftScrollFrame = leftSF,
rightFrame = rightFrame,
rightScrollFrame = rightSF,
ignoreCheckBox = ignoreCheckBox,
filter = filter,
clearBtn = clearBtn,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
addBtn.obj = widget
removeBtn.obj = widget
widget.ignoreCheckBox.obj = widget
widget.filter.obj = widget
widget.clearBtn.obj = widget
widget.leftFrame.obj = widget
widget.rightFrame.obj = widget
widget.frame.obj = widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

71
TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua

@ -0,0 +1,71 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local Type, Version = "TSMImage", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetSizeRatio(0.5)
self:SetWidth(100)
self:SetText()
self:SetImage()
end,
["OnWidthSet"] = function(self, w)
self:SetHeight(w*self.ratio)
end,
["SetSizeRatio"] = function(self, ratio)
self.ratio = ratio
end,
["SetImage"] = function(self, image)
self.image:SetTexture(image)
end,
["SetText"] = function(self, text)
self.text:SetText(text)
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
local image = frame:CreateTexture(nil, "ARTWORK")
image:SetAllPoints()
local text = frame:CreateFontString()
text:SetFont(TSMAPI.Design:GetContentFont("normal"))
text:SetTextColor(1, 1, 1, 1)
text:SetPoint("BOTTOMRIGHT", -2, 2)
local widget = {
frame = frame,
image = image,
text = text,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

68
TradeSkillMaster/GUI/TSMWidgets/TSMInlineGroup.lua

@ -0,0 +1,68 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua
-- This InlineGroup container is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local AceGUI = LibStub("AceGUI-3.0")
local Type, Version = "TSMInlineGroup", 2
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
local function Constructor()
local container = AceGUI:Create("InlineGroup")
container.type = Type
container.Add = TSMAPI.AddGUIElement
container.bgFrame = container.content:GetParent()
container.border = container.content:GetParent()
container.content:SetParent(container.frame)
local title = container.frame:CreateFontString(nil, "BACKGROUND")
title:SetPoint("TOPLEFT", 10, 0)
title:SetPoint("TOPRIGHT", -14, 0)
title:SetJustifyH("LEFT")
title:SetJustifyV("BOTTOM")
title:SetFont(TSMAPI.Design:GetBoldFont(), 18)
TSMAPI.Design:SetTitleTextColor(title)
container.titletext = title
container.HideTitle = function(self, hideTitle)
local frame = self.content:GetParent()
frame:ClearAllPoints()
if hideTitle then
self:SetTitle()
frame:SetPoint("TOPLEFT", 0, 0)
frame:SetPoint("BOTTOMRIGHT", -1, 3)
else
frame:SetPoint("TOPLEFT", 0, -17)
frame:SetPoint("BOTTOMRIGHT", -1, 3)
end
end
container.HideBorder = function(self, hideBorder)
if hideBorder then
self.border:Hide()
else
self.border:Show()
end
end
container.SetBackdrop = function(self, backdrop)
if backdrop then
TSMAPI.Design:SetContentColor(self.bgFrame)
else
TSMAPI.Design:SetFrameColor(self.bgFrame)
self.bgFrame:SetBackdropColor(0, 0, 0, 0)
end
end
AceGUI:RegisterAsContainer(container)
return container
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

113
TradeSkillMaster/GUI/TSMWidgets/TSMInteractiveLabel.lua

@ -0,0 +1,113 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Interactive.lua
-- This InteractiveLabel widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMInteractiveLabel", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local select, pairs = select, pairs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: GameFontHighlightSmall
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function Label_OnClick(frame, button)
frame.obj:Fire("OnClick", button)
AceGUI:ClearFocus()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:LabelOnAcquire()
self:SetHighlight()
self:SetHighlightTexCoord()
self:SetDisabled(false)
end,
["SetHighlight"] = function(self, ...)
self.highlight:SetTexture(...)
end,
["SetHighlightTexCoord"] = function(self, ...)
local c = select("#", ...)
if c == 4 or c == 8 then
self.highlight:SetTexCoord(...)
else
self.highlight:SetTexCoord(0, 1, 0, 1)
end
end,
["SetDisabled"] = function(self,disabled)
self.disabled = disabled
if disabled then
self.frame:EnableMouse(false)
self.label:SetTextColor(0.5, 0.5, 0.5)
else
self.frame:EnableMouse(true)
self.label:SetTextColor(1, 1, 1)
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
-- create a Label type that we will hijack
local label = AceGUI:Create("TSMLabel")
local frame = label.frame
frame:EnableMouse(true)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
frame:SetScript("OnMouseDown", Label_OnClick)
local bg = frame:CreateTexture(nil, "BACKGROUND")
bg:SetPoint("TOPLEFT", -2, 2)
bg:SetPoint("BOTTOMRIGHT", label.label, 2, -2)
TSMAPI.Design:SetContentColor(bg)
local highlight = frame:CreateTexture(nil, "HIGHLIGHT")
highlight:SetTexture(nil)
highlight:SetAllPoints()
highlight:SetBlendMode("ADD")
label.highlight = highlight
label.type = Type
label.LabelOnAcquire = label.OnAcquire
for method, func in pairs(methods) do
label[method] = func
end
return label
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

107
TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua

@ -0,0 +1,107 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Label.lua
-- This Label widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMLabel", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local max, select, pairs = math.max, select, pairs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function UpdateLabelAnchor(self)
if self.resizing then return end
local frame = self.frame
local width = frame.width or frame:GetWidth() or 0
local label = self.label
local height
label:ClearAllPoints()
label:SetPoint("TOPLEFT")
label:SetWidth(min(label:GetStringWidth(), width))
height = label:GetHeight()
self.resizing = true
frame:SetHeight(height)
frame.height = height
self.resizing = nil
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- set the flag to stop constant size updates
self.resizing = true
-- height is set dynamically by the text and image size
self:SetWidth(200)
self:SetText()
self:SetColor()
-- reset the flag
self.resizing = nil
-- run the update explicitly
UpdateLabelAnchor(self)
end,
["OnWidthSet"] = function(self, width)
UpdateLabelAnchor(self)
end,
["SetText"] = function(self, text)
self.label:SetText(text)
UpdateLabelAnchor(self)
end,
["SetColor"] = function(self, r, g, b)
if not (r and b and g) then
TSMAPI.Design:SetWidgetLabelColor(self.label)
else
self.label:SetTextColor(r, g, b)
end
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
frame:Hide()
local label = frame:CreateFontString(nil, "BACKGROUND")
label:SetJustifyH("LEFT")
label:SetJustifyV("TOP")
label:SetFont(TSMAPI.Design:GetContentFont("normal"))
-- create widget
local widget = {
label = label,
frame = frame,
type = Type,
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

388
TradeSkillMaster/GUI/TSMWidgets/TSMMainFrame.lua

@ -0,0 +1,388 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-Frame.lua
-- This Frame container is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMMainFrame", 2
local AceGUI = LibStub("AceGUI-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
local ICON_TEXT_COLOR = {165/255, 168/255, 188/255, .7}
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Frame_OnClose(frame)
frame.obj:Fire("OnClose")
end
local function CloseButton_OnClick(frame)
PlaySound("gsTitleOptionExit")
frame.obj:Hide()
end
local function Frame_OnMouseDown(frame)
frame.toMove:GetScript("OnMouseDown")(frame.toMove)
AceGUI:ClearFocus()
end
local function Frame_OnMouseUp(frame)
frame.toMove:GetScript("OnMouseUp")(frame.toMove)
AceGUI:ClearFocus()
end
local function Sizer_OnMouseUp(mover)
local frame = mover:GetParent()
frame:StopMovingOrSizing()
frame:SavePositionAndSize()
local self = frame.obj
local status = self.status or self.localstatus
status.width = frame:GetWidth()
status.height = frame:GetHeight()
status.top = frame:GetTop()
status.left = frame:GetLeft()
end
local function Sizer_OnMouseDown(frame)
frame:GetParent():StartSizing("BOTTOMRIGHT")
AceGUI:ClearFocus()
end
local function Icon_OnEnter(btn)
btn.dark:Hide()
GameTooltip:SetOwner(btn, btn:GetParent().tooltipAnchor)
GameTooltip:SetText(btn.title)
GameTooltip:Show()
end
local function Icon_OnLeave(btn)
if btn.obj.selected ~= btn then
btn.dark:Show()
end
GameTooltip:Hide()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self.frame:RefreshPosition()
self.frame:SetFrameStrata("MEDIUM")
self:SetTitle()
self:ApplyStatus()
self:Show()
end,
["OnRelease"] = function(self)
self.status = nil
wipe(self.localstatus)
end,
["LayoutIcons"] = function(self)
for _, container in ipairs({self.topLeftIcons, self.topRightIcons}) do
if type(container.icons) == "table" and container.icons[1] then
local numIcons = #container.icons
local iconSize = container.icons[1]:GetHeight()
local spacing = (container:GetWidth() - numIcons * iconSize) / (numIcons + 1)
local width = iconSize
if spacing < 1 then
spacing = 1
width = (container:GetWidth() - (numIcons - 1) * spacing) / numIcons
end
for i, icon in ipairs(container.icons) do
icon:SetPoint("TOPLEFT", spacing+(i-1)*(width+spacing), 0)
icon:SetWidth(width)
end
end
end
end,
["OnWidthSet"] = function(self, width)
self.content.width = self.content:GetWidth()
self.topLeftIcons:ClearAllPoints()
self.topLeftIcons:SetPoint("TOPLEFT", 5, 20)
self.topLeftIcons:SetPoint("TOPRIGHT", self.frame, "TOP", -115, 20)
self.topLeftIcons:SetHeight(51)
self.topRightIcons:ClearAllPoints()
self.topRightIcons:SetPoint("TOPRIGHT", -5, 20)
self.topRightIcons:SetPoint("TOPLEFT", self.frame, "TOP", 115, 20)
self.topRightIcons:SetHeight(51)
self:LayoutIcons()
end,
["OnHeightSet"] = function(self, height)
self.content.height = self.content:GetHeight()
end,
["SetTitle"] = function(self, title)
self.titletext:SetText(title)
end,
["SetIconText"] = function(self, title)
self.icontext:SetText(title)
end,
["SetIconLabels"] = function(self, topLeft, topRight)
self.topLeftIcons.label = topLeft
self.topRightIcons.label = topRight
end,
["Hide"] = function(self)
self.frame:Hide()
end,
["Show"] = function(self)
self.frame:Show()
end,
["UpdateSelected"] = function(self)
for _, container in ipairs({self.topLeftIcons, self.topRightIcons}) do
if type(container.icons) == "table" then
for _, icon in ipairs(container.icons) do
icon.dark:Show()
end
end
end
self.selected.dark:Hide()
end,
["AddIcon"] = function(self, info)
local container = self[info.where.."Icons"]
assert(container, "Invalid icon container.")
local size = 51
local btn = CreateFrame("Button", nil, container)
btn:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=2})
btn:SetBackdropBorderColor(0, 0, 0, 0.5)
btn:SetHeight(size)
btn:SetWidth(size)
btn.title = info.name
btn.info = info
btn.obj = self
info.frame = btn
local image = btn:CreateTexture(nil, "BACKGROUND")
image:SetAllPoints()
image:SetTexture(info.texture)
image:SetTexCoord(0.08, 0.922, 0.09, 0.918)
image:SetVertexColor(1, 1, 1)
btn.image = image
local dark = btn:CreateTexture(nil, "OVERLAY")
dark:SetAllPoints(image)
dark:SetTexture(0, 0, 0, .3)
dark:SetBlendMode("BLEND")
btn.dark = dark
btn:SetScript("OnEnter", Icon_OnEnter)
btn:SetScript("OnLeave", Icon_OnLeave)
btn:SetScript("OnClick", function(btn)
if #self.children > 0 then
self:ReleaseChildren()
end
self:SetTitle(btn.title)
btn.info.loadGUI(self)
self.selected = btn
self:UpdateSelected()
end)
local highlight = btn:CreateTexture(nil, "HIGHLIGHT")
highlight:SetAllPoints(image)
highlight:SetTexture(1, 1, 1, .2)
highlight:SetBlendMode("ADD")
btn.highlight = highlight
container.icons = container.icons or {}
tinsert(container.icons, btn)
self:LayoutIcons()
if not container.textLabel then
local label = container:CreateFontString()
label:SetHeight(12)
label:SetJustifyH("CENTER")
label:SetJustifyV("CENTER")
label:SetFont(TSMAPI.Design:GetContentFont("small"))
TSMAPI.Design:SetIconRegionColor(label)
label:SetText(container.label)
label:SetPoint("TOP", 0, -53)
container.tooltipAnchor = "ANCHOR_TOP"
container.textLabel = label
-- make the lines that extend the width of the container out from the label
local leftHLine = container:CreateTexture()
leftHLine:SetPoint("TOPRIGHT", label, "TOPLEFT", -2, -6)
leftHLine:SetHeight(1)
TSMAPI.Design:SetIconRegionColor(leftHLine)
local rightHLine = container:CreateTexture()
rightHLine:SetPoint("TOPLEFT", label, "TOPRIGHT", 2, -6)
rightHLine:SetHeight(1)
TSMAPI.Design:SetIconRegionColor(rightHLine)
leftHLine:SetPoint("TOPLEFT", 20, -59)
rightHLine:SetPoint("TOPRIGHT", -20, -59)
end
end,
-- called to set an external table to store status in
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
self:ApplyStatus()
end,
["ApplyStatus"] = function(self)
local status = self.status or self.localstatus
local frame = self.frame
self:SetWidth(status.width or self.frame:GetWidth())
self:SetHeight(status.height or self.frame:GetHeight())
frame:ClearAllPoints()
if status.top and status.left then
frame:SetPoint("TOP", UIParent, "BOTTOM", 0, status.top)
frame:SetPoint("LEFT", UIParent, "LEFT", status.left, 0)
else
frame:SetPoint("CENTER")
end
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frameName = Type..AceGUI:GetNextWidgetNum(Type)
local frameDefaults = {
x = UIParent:GetWidth()/2,
y = UIParent:GetHeight()/2,
width = 823,
height = 686,
scale = 1,
}
local frame = TSMAPI:CreateMovableFrame(frameName, frameDefaults)
frame:SetFrameStrata("MEDIUM")
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:SetResizable(true)
frame:SetMinResize(600, 400)
frame:SetScript("OnHide", Frame_OnClose)
frame.toMove = frame
tinsert(UISpecialFrames, frameName)
local closebutton = CreateFrame("Button", nil, frame)
TSMAPI.Design:SetContentColor(closebutton)
local highlight = closebutton:CreateTexture(nil, "HIGHLIGHT")
highlight:SetAllPoints()
highlight:SetTexture(1, 1, 1, .2)
highlight:SetBlendMode("BLEND")
closebutton.highlight = highlight
closebutton:SetPoint("BOTTOMRIGHT", -29, -14)
closebutton:SetHeight(29)
closebutton:SetWidth(86)
closebutton:SetScript("OnClick", CloseButton_OnClick)
closebutton:Show()
local label = closebutton:CreateFontString()
label:SetPoint("TOP")
label:SetJustifyH("CENTER")
label:SetJustifyV("CENTER")
label:SetHeight(28)
label:SetFont(TSMAPI.Design:GetContentFont(), 28)
TSMAPI.Design:SetWidgetTextColor(label)
label:SetText(CLOSE)
closebutton:SetFontString(label)
local iconBtn = CreateFrame("Button", nil, frame)
iconBtn:SetWidth(286)
iconBtn:SetHeight(286)
iconBtn:SetPoint("TOP", 0, 174)
iconBtn:SetScript("OnMouseDown", Frame_OnMouseDown)
iconBtn:SetScript("OnMouseUp", Frame_OnMouseUp)
iconBtn.toMove = frame
local icon = iconBtn:CreateTexture()
icon:SetAllPoints()
icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Pocket")
frame.icon = icon
local sizer = CreateFrame("Frame", nil, frame)
sizer:SetPoint("BOTTOMRIGHT", -2, 2)
sizer:SetWidth(20)
sizer:SetHeight(20)
sizer:EnableMouse()
sizer:SetScript("OnMouseDown",Sizer_OnMouseDown)
sizer:SetScript("OnMouseUp", Sizer_OnMouseUp)
local image = sizer:CreateTexture(nil, "BACKGROUND")
image:SetAllPoints()
image:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\Sizer")
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", 11, -62)
content:SetPoint("BOTTOMRIGHT", -11, 20)
local titletext = frame:CreateFontString()
titletext:SetPoint("TOP", 0, -32)
titletext:SetHeight(22)
titletext:SetJustifyH("CENTER")
titletext:SetJustifyV("CENTER")
titletext:SetFont(TSMAPI.Design:GetContentFont(), 22)
TSMAPI.Design:SetTitleTextColor(titletext)
local icontext = iconBtn:CreateFontString(nil, "OVERLAY")
icontext:SetPoint("TOP", frame, "TOP", 0, 14)
icontext:SetHeight(29)
icontext:SetJustifyH("CENTER")
icontext:SetJustifyV("CENTER")
icontext:SetFont(TSMAPI.Design:GetContentFont(), 27)
icontext:SetTextColor(unpack(ICON_TEXT_COLOR))
-- local helpButton = CreateFrame("Button", nil, frame, "MainHelpPlateButton")
local helpButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
helpButton:SetPoint("BOTTOMLEFT", -10, -30)
helpButton:SetScript("OnEnter", function(self)
HelpPlateTooltip.ArrowRIGHT:Show()
HelpPlateTooltip.ArrowGlowRIGHT:Show()
HelpPlateTooltip:SetPoint("LEFT", self, "RIGHT", 10, 0)
HelpPlateTooltip.Text:SetText(L["Click this to open TSM Assistant."])
HelpPlateTooltip:Show()
end)
helpButton:SetScript("OnLeave", function(self)
HelpPlateTooltip.ArrowRIGHT:Hide()
HelpPlateTooltip.ArrowGlowRIGHT:Hide()
HelpPlateTooltip:ClearAllPoints()
HelpPlateTooltip:Hide()
end)
helpButton:SetScript("OnClick", TSM.Assistant.Open)
local widget = {
type = Type,
localstatus = {},
frame = frame,
-- container for children
content = content,
-- changable labels
titletext = titletext,
icontext = icontext,
-- containers for the icons - size/pos set by OnWidthSet
topLeftIcons = CreateFrame("Frame", nil, frame),
topRightIcons = CreateFrame("Frame", nil, frame),
}
for method, func in pairs(methods) do
widget[method] = func
end
closebutton.obj = widget
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

85
TradeSkillMaster/GUI/TSMWidgets/TSMMultiLabel.lua

@ -0,0 +1,85 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-MultiLabel.lua
-- This MultiLabel widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMMultiLabel", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- height is set dynamically by the text size
self:SetWidth(200)
for i=1, #self.labels do
self.labels[i]:SetText()
end
end,
["OnWidthSet"] = function(self, width)
self:SetLabels(self.info)
end,
["SetLabels"] = function(self, info)
self.info = info
local totalWidth = self.frame:GetWidth() or 0
local usedWidth = 0
local maxHeight = 0
for i=1, #info do
if not self.labels[i] then
self.labels[i] = self.frame:CreateFontString(nil, "BACKGROUND")
self.labels[i]:SetFont(TSMAPI.Design:GetContentFont("normal"))
TSMAPI.Design:SetWidgetLabelColor(self.labels[i])
self.labels[i]:SetJustifyH("LEFT")
self.labels[i]:SetJustifyV("TOP")
end
self.labels[i]:SetText(info[i].text)
self.labels[i]:SetPoint("TOPLEFT", self.frame, "TOPLEFT", usedWidth, 0)
local labelWidth = totalWidth*(info[i].relativeWidth or 0)
labelWidth = min(labelWidth, totalWidth-usedWidth)
self.labels[i]:SetWidth(labelWidth)
usedWidth = usedWidth + labelWidth
if self.labels[i]:GetHeight() > maxHeight then
maxHeight = self.labels[i]:GetHeight()
end
end
self.frame:SetHeight(maxHeight)
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
frame:Hide()
local widget = {
labels = {},
info = {},
frame = frame,
type = Type,
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

21
TradeSkillMaster/GUI/TSMWidgets/TSMMultiLineEditBox.lua

@ -0,0 +1,21 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-TSMMultiLineEditBox.lua
-- This TSMMultiLineEditBox widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMMultiLineEditBox", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
local function Constructor()
local widget = AceGUI:Create("MultiLineEditBox")
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

223
TradeSkillMaster/GUI/TSMWidgets/TSMScrollFrame.lua

@ -0,0 +1,223 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua
-- This ScrollFrame container is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMScrollFrame", 2
local AceGUI = LibStub("AceGUI-3.0")
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs, assert, type = pairs, assert, type
local min, max, floor, abs = math.min, math.max, math.floor, math.abs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function FixScrollOnUpdate(frame)
frame:SetScript("OnUpdate", nil)
frame.obj:FixScroll()
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function ScrollFrame_OnMouseWheel(frame, value)
frame.obj:MoveScroll(value)
end
local function ScrollFrame_OnSizeChanged(frame)
frame:SetScript("OnUpdate", FixScrollOnUpdate)
end
local function ScrollBar_OnScrollValueChanged(frame, value)
frame.obj:SetScroll(value)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetScroll(0)
self:FixScroll()
end,
["OnRelease"] = function(self)
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
self.scrollframe:SetPoint("BOTTOMRIGHT")
self.scrollbar:Hide()
self.scrollBarShown = nil
self.content.height, self.content.width = nil, nil
end,
["SetScroll"] = function(self, value)
local status = self.status or self.localstatus
local viewheight = self.scrollframe:GetHeight()
local height = self.content:GetHeight()
local offset
if viewheight > height then
offset = 0
else
offset = floor((height - viewheight) / 1000.0 * value)
end
self.content:ClearAllPoints()
self.content:SetPoint("TOPLEFT", 0, offset)
self.content:SetPoint("TOPRIGHT", 0, offset)
status.offset = offset
status.scrollvalue = value
end,
["MoveScroll"] = function(self, value)
local status = self.status or self.localstatus
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
if self.scrollBarShown then
local diff = height - viewheight
local delta = 1
if value < 0 then
delta = -1
end
self.scrollbar:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000))
end
end,
["FixScroll"] = function(self)
if self.updateLock then return end
self.updateLock = true
local status = self.status or self.localstatus
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
local offset = status.offset or 0
local curvalue = self.scrollbar:GetValue()
-- Give us a margin of error of 2 pixels to stop some conditions that i would blame on floating point inaccuracys
-- No-one is going to miss 2 pixels at the bottom of the frame, anyhow!
if viewheight < height + 2 then
if self.scrollBarShown then
self.scrollBarShown = nil
self.scrollbar:Hide()
self.scrollbar:SetValue(0)
self.scrollframe:SetPoint("BOTTOMRIGHT")
self:DoLayout()
end
else
if not self.scrollBarShown then
self.scrollBarShown = true
self.scrollbar:Show()
self.scrollframe:SetPoint("BOTTOMRIGHT", -20, 0)
self:DoLayout()
end
local value = (offset / (viewheight - height) * 1000)
if value > 1000 then value = 1000 end
self.scrollbar:SetValue(value)
self:SetScroll(value)
if value < 1000 then
self.content:ClearAllPoints()
self.content:SetPoint("TOPLEFT", 0, offset)
self.content:SetPoint("TOPRIGHT", 0, offset)
status.offset = offset
end
end
self.updateLock = nil
end,
["LayoutFinished"] = function(self, width, height)
self.content:SetHeight(height or (0 + 20))
self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
-- no idea why, but this MUST be here to avoid glitches with the scrollbar not showing up when it should
self.content:GetHeight()
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
if not status.scrollvalue then
status.scrollvalue = 0
end
end,
["OnWidthSet"] = function(self, width)
local content = self.content
content.width = width
end,
["OnHeightSet"] = function(self, height)
local content = self.content
content.height = height
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
local num = AceGUI:GetNextWidgetNum(Type)
local scrollframe = CreateFrame("ScrollFrame", nil, frame)
scrollframe:SetPoint("TOPLEFT")
scrollframe:SetPoint("BOTTOMRIGHT")
scrollframe:EnableMouseWheel(true)
scrollframe:SetScript("OnMouseWheel", ScrollFrame_OnMouseWheel)
scrollframe:SetScript("OnSizeChanged", ScrollFrame_OnSizeChanged)
local scrollbar = CreateFrame("Slider", ("TSMScrollFrame%dScrollBar"):format(num), scrollframe, "UIPanelScrollBarTemplate")
scrollbar:SetPoint("TOPLEFT", scrollframe, "TOPRIGHT", 6, -4)
scrollbar:SetPoint("BOTTOMLEFT", scrollframe, "BOTTOMRIGHT", 6, 4)
scrollbar:SetMinMaxValues(0, 1000)
scrollbar:SetValueStep(1)
scrollbar:SetValue(0)
scrollbar:SetWidth(12)
scrollbar:Hide()
scrollbar:SetScript("OnValueChanged", ScrollBar_OnScrollValueChanged)
local thumbTex = scrollbar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetContentColor(thumbTex)
thumbTex:SetHeight(150)
thumbTex:SetWidth(scrollbar:GetWidth())
local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
scrollbg:SetAllPoints(scrollbar)
TSMAPI.Design:SetFrameColor(scrollbg)
_G[scrollbar:GetName().."ScrollUpButton"]:Hide()
_G[scrollbar:GetName().."ScrollDownButton"]:Hide()
--Container Support
local content = CreateFrame("Frame", nil, scrollframe)
content:SetPoint("TOPLEFT")
content:SetPoint("TOPRIGHT")
content:SetHeight(400)
scrollframe:SetScrollChild(content)
local widget = {
localstatus = { scrollvalue = 0 },
scrollframe = scrollframe,
scrollbar = scrollbar,
content = content,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
scrollframe.obj, scrollbar.obj = widget, widget
widget.Add = TSMAPI.AddGUIElement
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

21
TradeSkillMaster/GUI/TSMWidgets/TSMSimpleGroup.lua

@ -0,0 +1,21 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This SimpleGroup container is modified to fit TSM's theme / needs
local Type, Version = "TSMSimpleGroup", 2
local AceGUI = LibStub("AceGUI-3.0")
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
local function Constructor()
local container = AceGUI:Create("SimpleGroup")
container.type = Type
container.Add = TSMAPI.AddGUIElement
return AceGUI:RegisterAsContainer(container)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

279
TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua

@ -0,0 +1,279 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Slider.lua
-- This Slider widget is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMSlider", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local min, max, floor = math.min, math.max, math.floor
local tonumber, pairs = tonumber, pairs
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function UpdateText(self)
local value = self.value or 0
if self.ispercent then
self.editbox:SetText(("%s%%"):format(floor(value * 1000 + 0.5) / 10))
else
self.editbox:SetText(floor(value * 100 + 0.5) / 100)
end
end
local function UpdateLabels(self)
local min, max = (self.min or 0), (self.max or 100)
if self.ispercent then
self.lowtext:SetFormattedText("%s%%", (min * 100))
self.hightext:SetFormattedText("%s%%", (max * 100))
else
self.lowtext:SetText(min)
self.hightext:SetText(max)
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function Frame_OnMouseDown(frame)
frame.obj.slider:EnableMouseWheel(true)
AceGUI:ClearFocus()
end
local function Slider_OnValueChanged(frame)
local self = frame.obj
if not frame.setup then
local newvalue = frame:GetValue()
-- workaround for 5.4 issues
if self.step and self.step > 0 then
local min_value = self.min or 0
newvalue = floor((newvalue - min_value) / self.step + 0.5) * self.step + min_value
end
if newvalue ~= self.value and not self.disabled then
self.value = newvalue
self:Fire("OnValueChanged", newvalue)
end
if self.value then
UpdateText(self)
end
end
end
local function Slider_OnMouseUp(frame, button)
local self = frame.obj
self.slider:EnableMouseWheel(true)
self:Fire("OnMouseUp", self.value)
end
local function Slider_OnMouseWheel(frame, v)
local self = frame.obj
if not self.disabled then
local value = self.value
if v > 0 then
value = min(value + (self.step or 1), self.max)
else
value = max(value - (self.step or 1), self.min)
end
self.slider:SetValue(value)
end
end
local function EditBox_OnMouseUp(frame)
local self = frame.obj
end
local function EditBox_OnEscapePressed(frame)
frame:ClearFocus()
end
local function EditBox_OnEnterPressed(frame)
local self = frame.obj
local value = frame:GetText()
if self.ispercent then
value = value:gsub('%%', '')
value = tonumber(value) / 100
else
value = tonumber(value)
end
if value then
PlaySound("igMainMenuOptionCheckBoxOn")
self.slider:SetValue(value)
self:Fire("OnMouseUp", value)
frame:ClearFocus()
end
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetWidth(200)
self:SetHeight(44)
self:SetDisabled(false)
self:SetIsPercent(nil)
self:SetSliderValues(0,100,1)
self:SetValue(0)
self.slider:EnableMouseWheel(false)
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
TSMAPI.Design:SetWidgetTextColor(self.editbox, disabled)
self.slider:EnableMouse(not disabled)
self.editbox:EnableMouse(not disabled)
TSMAPI.Design:SetWidgetLabelColor(self.label, disabled)
TSMAPI.Design:SetWidgetLabelColor(self.hightext, disabled)
TSMAPI.Design:SetWidgetLabelColor(self.lowtext, disabled)
if disabled then
self.editbox:ClearFocus()
end
end,
["SetValue"] = function(self, value)
self.slider.setup = true
self.slider:SetValue(value)
self.value = value
UpdateText(self)
self.slider.setup = nil
end,
["GetValue"] = function(self)
return self.value
end,
["SetLabel"] = function(self, text)
self.label:SetText(text)
end,
["SetSliderValues"] = function(self, min, max, step)
local frame = self.slider
frame.setup = true
self.min = min
self.max = max
self.step = step
frame:SetMinMaxValues(min or 0,max or 100)
UpdateLabels(self)
frame:SetValueStep(step or 1)
if self.value then
frame:SetValue(self.value)
end
frame.setup = nil
end,
["SetIsPercent"] = function(self, value)
self.ispercent = value
UpdateLabels(self)
UpdateText(self)
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
frame:EnableMouse(true)
frame:SetScript("OnMouseDown", Frame_OnMouseDown)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
local label = frame:CreateFontString(nil, "OVERLAY")
label:SetPoint("TOPLEFT", 0, -2)
label:SetPoint("TOPRIGHT", 0, -2)
label:SetJustifyH("CENTER")
label:SetHeight(15)
label:SetFont(TSMAPI.Design:GetContentFont("normal"))
local slider = CreateFrame("Slider", nil, frame)
slider:SetOrientation("HORIZONTAL")
slider:SetHeight(6)
slider:SetHitRectInsets(0, 0, -10, 0)
slider:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 3, -4)
slider:SetPoint("TOPRIGHT", label, "BOTTOMRIGHT", -20, -4)
slider:SetValue(0)
slider:SetScript("OnValueChanged",Slider_OnValueChanged)
slider:SetScript("OnEnter", Control_OnEnter)
slider:SetScript("OnLeave", Control_OnLeave)
slider:SetScript("OnMouseUp", Slider_OnMouseUp)
slider:SetScript("OnMouseWheel", Slider_OnMouseWheel)
TSMAPI.Design:SetFrameColor(slider)
local thumbTex = slider:CreateTexture(nil, "ARTWORK")
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetContentColor(thumbTex)
thumbTex:SetHeight(15)
thumbTex:SetWidth(8)
slider:SetThumbTexture(thumbTex)
local lowtext = slider:CreateFontString(nil, "ARTWORK")
lowtext:SetFont(TSMAPI.Design:GetContentFont("small"))
lowtext:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 2, -4)
local hightext = slider:CreateFontString(nil, "ARTWORK")
hightext:SetFont(TSMAPI.Design:GetContentFont("small"))
hightext:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -2, -4)
local editbox = CreateFrame("EditBox", nil, frame)
editbox:SetAutoFocus(false)
editbox:SetPoint("TOP", slider, "BOTTOM", 0, -6)
editbox:SetHeight(15)
editbox:SetWidth(70)
editbox:SetJustifyH("CENTER")
editbox:EnableMouse(true)
TSMAPI.Design:SetContentColor(editbox)
editbox:SetScript("OnEnterPressed", EditBox_OnEnterPressed)
editbox:SetScript("OnEscapePressed", EditBox_OnEscapePressed)
editbox:SetScript("OnEnter", Control_OnEnter)
editbox:SetScript("OnLeave", Control_OnLeave)
editbox:SetFont(TSMAPI.Design:GetContentFont("normal"))
editbox:SetShadowColor(0, 0, 0, 0)
local widget = {
label = label,
slider = slider,
lowtext = lowtext,
hightext = hightext,
editbox = editbox,
alignoffset = 25,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
slider.obj, editbox.obj, frame.obj = widget, widget, widget
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

312
TradeSkillMaster/GUI/TSMWidgets/TSMTabGroup.lua

@ -0,0 +1,312 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua
-- This TabGroup container is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMTabGroup", 2
local AceGUI = LibStub("AceGUI-3.0")
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function TabResize(tab, padding, width)
local tabName = tab:GetName()
local sideWidths = 8
tab:SetWidth(width + padding + sideWidths)
end
local function UpdateTabLook(frame)
if frame.disabled then
TSMAPI.Design:SetWidgetLabelColor(frame.text, true)
frame:Disable()
frame.text = frame:GetText()
frame.bottom:Hide()
elseif frame.selected then
TSMAPI.Design:SetWidgetLabelColor(frame.text)
frame:Disable()
TSMAPI.Design:SetFrameColor(frame.image)
frame.bottom:Show()
frame:SetHeight(29)
if GameTooltip:IsOwned(frame) then
GameTooltip:Hide()
end
else
TSMAPI.Design:SetWidgetTextColor(frame.text)
frame:Enable()
TSMAPI.Design:SetContentColor(frame.image)
frame.bottom:Hide()
frame:SetHeight(24)
end
end
local function Tab_SetText(frame, text)
frame:_SetText(text)
local width = frame.obj.frame.width or frame.obj.frame:GetWidth() or 0
TabResize(frame, 0, frame:GetFontString():GetStringWidth())
end
local function Tab_SetSelected(frame, selected)
frame.selected = selected
UpdateTabLook(frame)
end
local function Tab_SetDisabled(frame, disabled)
frame.disabled = disabled
UpdateTabLook(frame)
end
local function BuildTabsOnUpdate(frame)
local self = frame.obj
self:BuildTabs()
frame:SetScript("OnUpdate", nil)
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Tab_OnClick(frame)
if not (frame.selected or frame.disabled) then
PlaySound("igCharacterInfoTab")
frame.obj:SelectTab(frame.value)
end
end
local function Tab_OnEnter(frame)
local self = frame.obj
self:Fire("OnTabEnter", self.tabs[frame.id].value, frame)
end
local function Tab_OnLeave(frame)
local self = frame.obj
self:Fire("OnTabLeave", self.tabs[frame.id].value, frame)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self) end,
["OnRelease"] = function(self)
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
self.tablist = nil
for _, tab in pairs(self.tabs) do
tab:Hide()
end
end,
["CreateTab"] = function(self, id)
local tabname = ("TSMTabGroup%dTab%d"):format(self.num, id)
local tab = CreateFrame("Button", tabname, self.border)
tab.obj = self
tab.id = id
tab:SetHeight(24)
TSMAPI.Design:SetFrameColor(tab)
tab:SetBackdropColor(0, 0, 0, 0)
local image = tab:CreateTexture(nil, "BACKGROUND")
image:SetAllPoints()
TSMAPI.Design:SetContentColor(image)
tab.image = image
local bottom = tab:CreateTexture(nil, "OVERLAY")
bottom:SetHeight(2)
bottom:SetPoint("BOTTOMLEFT", 1, -1)
bottom:SetPoint("BOTTOMRIGHT", -1, -1)
TSMAPI.Design:SetFrameColor(bottom)
tab.bottom = bottom
local highlight = tab:CreateTexture(nil, "HIGHLIGHT")
highlight:SetAllPoints()
highlight:SetTexture(1, 1, 1, .2)
highlight:SetBlendMode("BLEND")
tab.highlight = highlight
tab.text = tab:CreateFontString()
tab.text:SetPoint("LEFT", 3, -1)
tab.text:SetPoint("RIGHT", -3, -1)
tab.text:SetJustifyH("CENTER")
tab.text:SetJustifyV("CENTER")
tab.text:SetFont(TSMAPI.Design:GetContentFont(), 18)
tab:SetFontString(tab.text)
tab:SetScript("OnClick", Tab_OnClick)
tab:SetScript("OnEnter", Tab_OnEnter)
tab:SetScript("OnLeave", Tab_OnLeave)
tab._SetText = tab.SetText
tab.SetText = Tab_SetText
tab.SetSelected = Tab_SetSelected
tab.SetDisabled = Tab_SetDisabled
return tab
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
end,
["SelectTab"] = function(self, value)
local status = self.status or self.localstatus
local found
for i, v in ipairs(self.tabs) do
if v.value == value then
v:SetSelected(true)
found = true
else
v:SetSelected(false)
end
end
status.selected = value
if found then
self:Fire("OnGroupSelected", value)
end
end,
["SetTabs"] = function(self, tabs)
self.tablist = tabs
self:BuildTabs()
end,
["ReloadTab"] = function(self)
local status = self.status or self.localstatus
if status and status.selected then
self:Fire("OnGroupSelected", status.selected)
end
end,
["BuildTabs"] = function(self)
local status = self.status or self.localstatus
local tablist = self.tablist
local tabs = self.tabs
if not tablist then return end
local numTabs = #tablist
local width = self.frame.width or self.frame:GetWidth() or 0
-- Show tabs and set text.
for i, v in ipairs(tablist) do
local tab = tabs[i]
if not tab then
tab = self:CreateTab(i)
tabs[i] = tab
end
tab:Show()
tab:SetText(v.text)
tab:SetDisabled(v.disabled)
tab.value = v.value
end
-- hide tabs which aren't in use
for i=numTabs+1, #tabs do
tabs[i]:Hide()
end
--anchor the rows as defined and resize tabs
for i=1, numTabs do
local tab = tabs[i]
tab:ClearAllPoints()
if i == 1 then
tab:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", 5, -31)
else
tab:SetPoint("BOTTOMLEFT", tabs[i-1], "BOTTOMRIGHT", 4, 0)
end
end
for i=1, numTabs do
TabResize(tabs[i], 4, tabs[i]:GetFontString():GetStringWidth())
end
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 60
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
self:BuildTabs(self)
self.frame:SetScript("OnUpdate", BuildTabsOnUpdate)
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 30
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["LayoutFinished"] = function(self, width, height)
if self.noAutoHeight then return end
self:SetHeight((height or 0) + 30)
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local PaneBackdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame",nil,UIParent)
frame:SetHeight(100)
frame:SetWidth(100)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
local border = CreateFrame("Frame", nil, frame)
border:SetPoint("TOPLEFT", 1, -30)
border:SetPoint("BOTTOMRIGHT", -1, 3)
TSMAPI.Design:SetFrameColor(border)
local content = CreateFrame("Frame", nil, border)
content:SetPoint("TOPLEFT", 8, -8)
content:SetPoint("BOTTOMRIGHT", -8, 8)
local widget = {
num = num,
frame = frame,
localstatus = {},
border = border,
tabs = {},
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
widget.Add = TSMAPI.AddGUIElement
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

735
TradeSkillMaster/GUI/TSMWidgets/TSMTreeGroup.lua

@ -0,0 +1,735 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua
-- This TreeGroup container is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMTreeGroup", 2
local AceGUI = LibStub("AceGUI-3.0")
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type
local math_min, math_max, floor = math.min, math.max, floor
local select, tremove, unpack, tconcat = select, table.remove, unpack, table.concat
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
-- Recycling functions
local new, del
do
local pool = setmetatable({},{__mode='k'})
function new()
local t = next(pool)
if t then
pool[t] = nil
return t
else
return {}
end
end
function del(t)
for k in pairs(t) do
t[k] = nil
end
pool[t] = true
end
end
local DEFAULT_TREE_WIDTH = 175
local DEFAULT_TREE_SIZABLE = true
local HIGHLIGHT_COLOR = {80/255, 180/255, 180/255}
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function GetButtonUniqueValue(line)
local parent = line.parent
if parent and parent.value then
return GetButtonUniqueValue(parent).."\001"..line.value
else
return line.value
end
end
local function UpdateButton(button, treeline, selected, canExpand, isExpanded)
local self = button.obj
local toggle = button.toggle
local frame = self.frame
local text = treeline.text or ""
local icon = treeline.icon
local iconCoords = treeline.iconCoords
local level = treeline.level
local value = treeline.value
local uniquevalue = treeline.uniquevalue
local disabled = treeline.disabled
button.treeline = treeline
button.value = value
button.uniquevalue = uniquevalue
if selected then
button:LockHighlight()
button.selected = true
else
button:UnlockHighlight()
button.selected = false
end
local normalTexture = button:GetNormalTexture()
local line = button.line
button.level = level
if level == 1 then
button.text:SetFont(TSMAPI.Design:GetBoldFont(), 15)
TSMAPI.Design:SetTitleTextColor(button.text)
button.text:SetPoint("LEFT", (icon and 16 or 0) + 8, 2)
else
button.text:SetFont(TSMAPI.Design:GetContentFont("normal"))
TSMAPI.Design:SetWidgetLabelColor(button.text)
button.text:SetPoint("LEFT", (icon and 16 or 0) + 8 * level, 2)
end
if disabled then
button:EnableMouse(false)
button.text:SetText("|cff808080"..text..FONT_COLOR_CODE_CLOSE)
else
button.text:SetText(text)
button:EnableMouse(true)
end
if icon then
button.icon:SetTexture(icon)
button.icon:SetPoint("LEFT", 8 * level, (level == 1) and 0 or 1)
else
button.icon:SetTexture(nil)
end
if iconCoords then
button.icon:SetTexCoord(unpack(iconCoords))
else
button.icon:SetTexCoord(0, 1, 0, 1)
end
if canExpand then
if not isExpanded then
toggle.text:SetFont(TSMAPI.Design:GetBoldFont(), 24)
toggle.text:SetText("+")
else
toggle.text:SetFont(TSMAPI.Design:GetBoldFont(), 30)
toggle.text:SetText("-")
end
toggle:Show()
else
toggle:Hide()
end
end
local function ShouldDisplayLevel(tree)
local result = false
for k, v in ipairs(tree) do
if v.children == nil and v.visible ~= false then
result = true
elseif v.children then
result = result or ShouldDisplayLevel(v.children)
end
if result then return result end
end
return false
end
local function addLine(self, v, tree, level, parent)
local line = new()
line.value = v.value
line.text = v.text
line.icon = v.icon
line.iconCoords = v.iconCoords
line.disabled = v.disabled
line.tree = tree
line.level = level
line.parent = parent
line.visible = v.visible
line.uniquevalue = GetButtonUniqueValue(line)
if v.children then
line.hasChildren = true
else
line.hasChildren = nil
end
self.lines[#self.lines+1] = line
return line
end
--fire an update after one frame to catch the treeframes height
local function FirstFrameUpdate(frame)
local self = frame.obj
frame:SetScript("OnUpdate", nil)
self:RefreshTree()
end
local function BuildUniqueValue(...)
local n = select('#', ...)
if n == 1 then
return ...
else
return (...).."\001"..BuildUniqueValue(select(2,...))
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Expand_OnClick(frame)
local button = frame.button
local self = button.obj
local status = (self.status or self.localstatus).groups
status[button.uniquevalue] = not status[button.uniquevalue]
self:RefreshTree()
end
local function Button_OnClick(frame)
local self = frame.obj
self:Fire("OnClick", frame.uniquevalue, frame.selected)
if not frame.selected then
self:SetSelected(frame.uniquevalue)
frame.selected = true
frame:LockHighlight()
self:RefreshTree()
end
AceGUI:ClearFocus()
end
local function Button_OnDoubleClick(button)
local self = button.obj
local status = self.status or self.localstatus
local status = (self.status or self.localstatus).groups
status[button.uniquevalue] = not status[button.uniquevalue]
self:RefreshTree()
end
local function Button_OnEnter(frame)
local self = frame.obj
self:Fire("OnButtonEnter", frame.uniquevalue, frame)
if self.enabletooltips then
GameTooltip:SetOwner(frame, "ANCHOR_NONE")
GameTooltip:SetPoint("LEFT",frame,"RIGHT")
-- strip out color codes
local text = frame.text:GetText() or ""
text = gsub(text, "|cff[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "")
text = gsub(text, "|r", "")
GameTooltip:SetText(text, 1, .82, 0, 1)
GameTooltip:Show()
end
end
local function Button_OnLeave(frame)
local self = frame.obj
self:Fire("OnButtonLeave", frame.uniquevalue, frame)
if self.enabletooltips then
GameTooltip:Hide()
end
end
local function OnScrollValueChanged(frame, value)
if frame.obj.noupdate then return end
local self = frame.obj
local status = self.status or self.localstatus
status.scrollvalue = floor(value + 0.5)
self:RefreshTree()
AceGUI:ClearFocus()
end
local function Tree_OnSizeChanged(frame)
frame.obj:RefreshTree()
end
local function Tree_OnMouseWheel(frame, delta)
local self = frame.obj
if self.showscroll then
local scrollbar = self.scrollbar
local min, max = scrollbar:GetMinMaxValues()
local value = scrollbar:GetValue()
local newvalue = math_min(max,math_max(min,value - delta))
if value ~= newvalue then
scrollbar:SetValue(newvalue)
end
end
end
local function Dragger_OnLeave(frame)
frame:SetBackdropColor(1, 1, 1, 0)
end
local function Dragger_OnEnter(frame)
frame:SetBackdropColor(1, 1, 1, 0.8)
end
local function Dragger_OnMouseDown(frame)
local treeframe = frame:GetParent()
treeframe:StartSizing("RIGHT")
end
local function Dragger_OnMouseUp(frame)
local treeframe = frame:GetParent()
local self = treeframe.obj
local frame = treeframe:GetParent()
treeframe:StopMovingOrSizing()
treeframe:SetUserPlaced(false)
--Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize
treeframe:SetHeight(0)
treeframe:SetPoint("TOPLEFT", frame, "TOPLEFT",0,0)
treeframe:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT",0,0)
local status = self.status or self.localstatus
status.treewidth = treeframe:GetWidth()
treeframe.obj:Fire("OnTreeResize",treeframe:GetWidth())
-- recalculate the content width
treeframe.obj:OnWidthSet(status.fullwidth)
-- update the layout of the content
treeframe.obj:DoLayout()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetTreeWidth(DEFAULT_TREE_WIDTH, DEFAULT_TREE_SIZABLE)
self:EnableButtonTooltips(true)
end,
["OnRelease"] = function(self)
self.status = nil
for k, v in pairs(self.localstatus) do
if k == "groups" then
for k2 in pairs(v) do
v[k2] = nil
end
else
self.localstatus[k] = nil
end
end
self.localstatus.scrollvalue = 0
self.localstatus.treewidth = DEFAULT_TREE_WIDTH
self.localstatus.treesizable = DEFAULT_TREE_SIZABLE
end,
["EnableButtonTooltips"] = function(self, enable)
self.enabletooltips = enable
end,
["CreateButton"] = function(self)
local num = AceGUI:GetNextWidgetNum("TreeGroupButton")
local button = CreateFrame("Button", ("TSMTreeButton%d"):format(num), self.treeframe, "OptionsListButtonTemplate")
button.obj = self
local icon = button:CreateTexture(nil, "OVERLAY")
icon:SetWidth(14)
icon:SetHeight(14)
button.icon = icon
button.text:SetShadowColor(0, 0, 0, 0)
button.text:SetPoint("CENTER")
button:SetScript("OnClick",Button_OnClick)
button:SetScript("OnDoubleClick", Button_OnDoubleClick)
button:SetScript("OnEnter",Button_OnEnter)
button:SetScript("OnLeave",Button_OnLeave)
HIGHLIGHT_COLOR[4] = .5
button.highlight:SetVertexColor(unpack(HIGHLIGHT_COLOR))
button.toggle.button = button
button.toggle:SetScript("OnClick", Expand_OnClick)
local tex = button.toggle:CreateTexture()
tex:SetTexture(0, 0, 0, 0)
button.toggle:SetNormalTexture(tex)
button.toggle:SetPushedTexture(tex)
HIGHLIGHT_COLOR[4] = 1
button.toggle:GetHighlightTexture():SetVertexColor(unpack(HIGHLIGHT_COLOR))
button.toggle:GetHighlightTexture():SetBlendMode("ADD")
local text = button.toggle:CreateFontString()
TSMAPI.Design:SetWidgetLabelColor(text)
text:SetPoint("CENTER")
button.toggle.text = text
return button
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
if not status.groups then
status.groups = {}
end
if not status.scrollvalue then
status.scrollvalue = 0
end
if not status.treewidth then
status.treewidth = DEFAULT_TREE_WIDTH
end
if status.treesizable == nil then
status.treesizable = DEFAULT_TREE_SIZABLE
end
self:SetTreeWidth(status.treewidth,status.treesizable)
self:RefreshTree()
end,
--sets the tree to be displayed
["SetTree"] = function(self, tree, filter)
self.filter = filter
if tree then
assert(type(tree) == "table")
end
self.tree = tree
self:RefreshTree()
end,
["BuildLevel"] = function(self, tree, level, parent)
local groups = (self.status or self.localstatus).groups
local hasChildren = self.hasChildren
for i, v in ipairs(tree) do
if v.children then
if not self.filter or ShouldDisplayLevel(v.children) then
local line = addLine(self, v, tree, level, parent)
if groups[line.uniquevalue] then
self:BuildLevel(v.children, level+1, line)
end
end
elseif v.visible ~= false or not self.filter then
addLine(self, v, tree, level, parent)
end
end
end,
["RefreshTree"] = function(self,scrollToSelection)
local buttons = self.buttons
local lines = self.lines
for i, v in ipairs(buttons) do
v:Hide()
end
while lines[1] do
local t = tremove(lines)
for k in pairs(t) do
t[k] = nil
end
del(t)
end
if not self.tree then return end
--Build the list of visible entries from the tree and status tables
local status = self.status or self.localstatus
local groupstatus = status.groups
local tree = self.tree
local treeframe = self.treeframe
status.scrollToSelection = status.scrollToSelection or scrollToSelection -- needs to be cached in case the control hasn't been drawn yet (code bails out below)
self:BuildLevel(tree, 1)
local numlines = #lines
local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18))
if maxlines <= 0 then return end
local first, last
scrollToSelection = status.scrollToSelection
status.scrollToSelection = nil
if numlines <= maxlines then
--the whole tree fits in the frame
status.scrollvalue = 0
self:ShowScroll(false)
first, last = 1, numlines
else
self:ShowScroll(true)
--scrolling will be needed
self.noupdate = true
self.scrollbar:SetMinMaxValues(0, numlines - maxlines)
--check if we are scrolled down too far
if numlines - status.scrollvalue < maxlines then
status.scrollvalue = numlines - maxlines
end
self.noupdate = nil
first, last = status.scrollvalue+1, status.scrollvalue + maxlines
--show selection?
if scrollToSelection and status.selected then
local show
for i,line in ipairs(lines) do -- find the line number
if line.uniquevalue==status.selected then
show=i
end
end
if not show then
-- selection was deleted or something?
elseif show>=first and show<=last then
-- all good
else
-- scrolling needed!
if show<first then
status.scrollvalue = show-1
else
status.scrollvalue = show-maxlines
end
first, last = status.scrollvalue+1, status.scrollvalue + maxlines
end
end
if self.scrollbar:GetValue() ~= status.scrollvalue then
self.scrollbar:SetValue(status.scrollvalue)
end
end
local buttonnum = 1
for i = first, min(last, #lines) do
local line = lines[i]
local button = buttons[buttonnum]
if not button then
button = self:CreateButton()
buttons[buttonnum] = button
button:SetParent(treeframe)
button:SetFrameLevel(treeframe:GetFrameLevel()+1)
button:ClearAllPoints()
if buttonnum == 1 then
if self.showscroll then
button:SetPoint("TOPRIGHT", -22, -10)
button:SetPoint("TOPLEFT", 0, -10)
else
button:SetPoint("TOPRIGHT", 0, -10)
button:SetPoint("TOPLEFT", 0, -10)
end
else
button:SetPoint("TOPRIGHT", buttons[buttonnum-1], "BOTTOMRIGHT",0,0)
button:SetPoint("TOPLEFT", buttons[buttonnum-1], "BOTTOMLEFT",0,0)
end
end
UpdateButton(button, line, status.selected == line.uniquevalue, line.hasChildren, groupstatus[line.uniquevalue] )
button:Show()
buttonnum = buttonnum + 1
end
end,
["SetSelected"] = function(self, value)
local status = self.status or self.localstatus
if status.selected ~= value then
status.selected = value
self:Fire("OnGroupSelected", value)
end
end,
["Select"] = function(self, uniquevalue, ...)
self.filter = false
local status = self.status or self.localstatus
local groups = status.groups
local path = {...}
for i = 1, #path do
groups[tconcat(path, "\001", 1, i)] = true
end
status.selected = uniquevalue
self:RefreshTree(true)
self:Fire("OnGroupSelected", uniquevalue)
end,
["SelectByPath"] = function(self, ...)
self:Select(BuildUniqueValue(...), ...)
end,
["SelectByValue"] = function(self, uniquevalue)
self:Select(uniquevalue, ("\001"):split(uniquevalue))
end,
["ShowScroll"] = function(self, show)
self.showscroll = show
if show then
self.scrollbar:Show()
if self.buttons[1] then
self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
end
else
self.scrollbar:Hide()
if self.buttons[1] then
self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
end
end
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local treeframe = self.treeframe
local status = self.status or self.localstatus
status.fullwidth = width
local contentwidth = width - status.treewidth - 20
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
local maxtreewidth = math_min(400, width - 50)
if maxtreewidth > 100 and status.treewidth > maxtreewidth then
self:SetTreeWidth(maxtreewidth, status.treesizable)
end
treeframe:SetMaxResize(maxtreewidth, 1600)
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 20
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["SetTreeWidth"] = function(self, treewidth, resizable)
if not resizable then
if type(treewidth) == 'number' then
resizable = false
elseif type(treewidth) == 'boolean' then
resizable = treewidth
treewidth = DEFAULT_TREE_WIDTH
else
resizable = false
treewidth = DEFAULT_TREE_WIDTH
end
end
self.treeframe:SetWidth(treewidth)
self.dragger:EnableMouse(resizable)
local status = self.status or self.localstatus
status.treewidth = treewidth
status.treesizable = resizable
-- recalculate the content width
if status.fullwidth then
self:OnWidthSet(status.fullwidth)
end
end,
["GetTreeWidth"] = function(self)
local status = self.status or self.localstatus
return status.treewidth or DEFAULT_TREE_WIDTH
end,
["LayoutFinished"] = function(self, width, height)
if self.noAutoHeight then return end
self:SetHeight((height or 0) + 20)
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local DraggerBackdrop = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = nil,
tile = true, tileSize = 16, edgeSize = 0,
insets = { left = 3, right = 3, top = 7, bottom = 7 }
}
local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
local treeframe = CreateFrame("Frame", nil, frame)
treeframe:SetPoint("TOPLEFT")
treeframe:SetPoint("BOTTOMLEFT")
treeframe:SetWidth(DEFAULT_TREE_WIDTH)
treeframe:EnableMouseWheel(true)
treeframe:SetResizable(true)
treeframe:SetMinResize(100, 1)
treeframe:SetMaxResize(400, 1600)
treeframe:SetScript("OnUpdate", FirstFrameUpdate)
treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged)
treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel)
TSMAPI.Design:SetFrameColor(treeframe)
local dragger = CreateFrame("Frame", nil, treeframe)
dragger:SetWidth(10)
dragger:SetPoint("TOPLEFT", treeframe, "TOPRIGHT")
dragger:SetPoint("BOTTOMLEFT", treeframe, "BOTTOMRIGHT")
dragger:SetBackdrop(DraggerBackdrop)
dragger:SetBackdropColor(1, 1, 1, 0)
dragger:SetScript("OnEnter", Dragger_OnEnter)
dragger:SetScript("OnLeave", Dragger_OnLeave)
dragger:SetScript("OnMouseDown", Dragger_OnMouseDown)
dragger:SetScript("OnMouseUp", Dragger_OnMouseUp)
local scrollbar = CreateFrame("Slider", ("TSMTreeGroup%dScrollBar"):format(num), treeframe, "UIPanelScrollBarTemplate")
scrollbar:SetScript("OnValueChanged", nil)
scrollbar:SetPoint("TOPRIGHT", -10, -26)
scrollbar:SetPoint("BOTTOMRIGHT", -10, 26)
scrollbar:SetMinMaxValues(0,0)
scrollbar:SetValueStep(1)
scrollbar:SetValue(0)
scrollbar:SetWidth(12)
scrollbar:SetScript("OnValueChanged", OnScrollValueChanged)
local thumbTex = scrollbar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetContentColor(thumbTex)
thumbTex:SetHeight(150)
thumbTex:SetWidth(scrollbar:GetWidth())
local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
scrollbg:SetAllPoints(scrollbar)
TSMAPI.Design:SetFrameColor(scrollbg)
_G[scrollbar:GetName().."ScrollUpButton"]:Hide()
_G[scrollbar:GetName().."ScrollDownButton"]:Hide()
local border = CreateFrame("Frame",nil,frame)
border:SetPoint("TOPLEFT", dragger, "TOPRIGHT", -1, 0)
border:SetPoint("BOTTOMRIGHT")
TSMAPI.Design:SetFrameColor(border)
--Container Support
local content = CreateFrame("Frame", nil, border)
content:SetPoint("TOPLEFT", 6, -6)
content:SetPoint("BOTTOMRIGHT", -6, 6)
local widget = {
frame = frame,
lines = {},
levels = {},
buttons = {},
hasChildren = {},
localstatus = {groups={}, scrollvalue=0},
filter = false,
treeframe = treeframe,
dragger = dragger,
scrollbar = scrollbar,
border = border,
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
treeframe.obj, dragger.obj, scrollbar.obj = widget, widget, widget
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

169
TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua

@ -0,0 +1,169 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Window.lua
-- This Window container is modified to fit TSM's theme / needs
local TSM = select(2, ...)
local Type, Version = "TSMWindow", 2
local AceGUI = LibStub("AceGUI-3.0")
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs, assert, type = pairs, assert, type
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
--[[----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function frameOnClose(this)
this.obj:Fire("OnClose")
end
local function closeOnClick(this)
PlaySound("gsTitleOptionExit")
this.obj:Hide()
end
local function frameOnMouseDown(this)
this:StartMoving()
AceGUI:ClearFocus()
end
local function frameOnMouseUp(this)
this:StopMovingOrSizing()
end
--[[----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self.frame:SetParent(UIParent)
self.frame:SetFrameStrata("FULLSCREEN_DIALOG")
self:ApplyStatus()
self:Show()
end,
["OnRelease"] = function(self)
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
end,
["Show"] = function(self)
self.frame:Show()
end,
["Hide"] = function(self)
self.frame:Hide()
end,
["SetTitle"] = function(self,title)
self.titletext:SetText(title)
end,
["ApplyStatus"] = function(self)
local status = self.status or self.localstatus
local frame = self.frame
self:SetWidth(status.width or 700)
self:SetHeight(status.height or 500)
if status.top and status.left then
frame:SetPoint("TOP",UIParent,"BOTTOM",0,status.top)
frame:SetPoint("LEFT",UIParent,"LEFT",status.left,0)
else
frame:SetPoint("CENTER",UIParent,"CENTER")
end
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 34
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 57
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame")
frame:SetWidth(700)
frame:SetHeight(500)
frame:SetPoint("CENTER")
frame:EnableMouse(true)
frame:SetMovable(true)
frame:SetResizable(true)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
frame:SetScript("OnMouseDown", frameOnMouseDown)
frame:SetScript("OnMouseUp", frameOnMouseUp)
frame:SetScript("OnHide", frameOnClose)
TSMAPI.Design:SetFrameBackdropColor(frame)
local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", 2, 1)
close:SetScript("OnClick", closeOnClick)
local titletext = frame:CreateFontString(nil, "ARTWORK")
titletext:SetFont(TSMAPI.Design:GetBoldFont(), 18)
TSMAPI.Design:SetTitleTextColor(titletext)
titletext:SetPoint("TOP", 0, -4)
local line = frame:CreateTexture()
line:SetPoint("TOPLEFT", 2, -28)
line:SetPoint("TOPRIGHT", -2, -28)
line:SetHeight(2)
TSMAPI.Design:SetIconRegionColor(line)
--Container Support
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -32)
content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 13)
local widget = {
frame = frame,
type = Type,
localstatus = {},
content = content,
title = title,
titletext = titletext,
closebutton = close,
}
for method, func in pairs(methods) do
widget[method] = func
end
frame.obj, content.obj, close.obj = widget, widget, widget
widget.Add = TSMAPI.AddGUIElement
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type,Constructor,Version)

186
TradeSkillMaster/Items.lua

@ -0,0 +1,186 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskillmaster_warehousing --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains APIs related to items (itemLinks/itemStrings/etc)
local TSM = select(2, ...)
function TSMAPI:SafeTooltipLink(link)
-- if strmatch(link, "battlepet") then
-- local _, speciesID, level, breedQuality, maxHealth, power, speed, battlePetID = strsplit(":", link)
-- BattlePetToolTip_Show(tonumber(speciesID), tonumber(level), tonumber(breedQuality), tonumber(maxHealth), tonumber(power), tonumber(speed), gsub(gsub(link, "^(.*)%[", ""), "%](.*)$", ""))
-- else
GameTooltip:SetHyperlink(link)
-- end
end
function TSMAPI:GetItemString(item)
if type(item) == "string" then
item = item:trim()
end
if type(item) ~= "string" and type(item) ~= "number" then
return nil, "invalid arg type"
end
item = select(2, TSMAPI:GetSafeItemInfo(item)) or item
if tonumber(item) then
return "item:"..item..":0:0:0:0:0:0"
end
local itemInfo = {strfind(item, "|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%-?%d*):?(%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?")}
if not itemInfo[11] then return nil, "invalid link" end
itemInfo[11] = tonumber(itemInfo[11]) or 0
if itemInfo[4] == "item" then
for i=6, 10 do itemInfo[i] = 0 end
return table.concat(itemInfo, ":", 4, 11)
else
return table.concat(itemInfo, ":", 4, 7)
end
end
function TSMAPI:GetBaseItemString(itemString, doGroupLookup)
if type(itemString) ~= "string" then return end
if strsub(itemString, 1, 2) == "|c" then
-- this is an itemLink so get the itemString first
itemString = TSMAPI:GetItemString(itemString)
if not itemString then return end
end
local parts = {(":"):split(itemString)}
for i=3, #parts do
parts[i] = 0
end
local baseItemString = table.concat(parts, ":")
if not doGroupLookup then return baseItemString end
if TSM.db.profile.items[itemString] and TSM.db.profile.items[baseItemString] then
return itemString
elseif TSM.db.profile.items[baseItemString] then
return baseItemString
else
return itemString
end
end
local itemInfoCache = {}
local PET_CAGE_ITEM_INFO = {isDefault=true, 0, "Battle Pets", "", 1, "", "", 0}
function TSMAPI:GetSafeItemInfo(link)
if type(link) ~= "string" then return end
if not itemInfoCache[link] then
-- if strmatch(link, "battlepet:") then
-- local _, speciesID, level, quality, health, power, speed, petID = strsplit(":", link)
-- if not tonumber(speciesID) then return end
-- level, quality, health, power, speed, petID = level or 0, quality or 0, health or 0, power or 0, speed or 0, petID or "0"
-- local name, texture = C_PetJournal.GetPetInfoBySpeciesID(tonumber(speciesID))
-- if name == "" then return end
-- level, quality = tonumber(level), tonumber(quality)
-- petID = strsub(petID, 1, (strfind(petID, "|") or #petID)-1)
-- link = ITEM_QUALITY_COLORS[quality].hex.."|Hbattlepet:"..speciesID..":"..level..":"..quality..":"..health..":"..power..":"..speed..":"..petID.."|h["..name.."]|h|r"
-- if PET_CAGE_ITEM_INFO.isDefault then
-- local data = {select(5, GetItemInfo(82800))}
-- if #data > 0 then
-- PET_CAGE_ITEM_INFO = data
-- end
-- end
-- local minLvl, iType, _, stackSize, _, _, vendorPrice = unpack(PET_CAGE_ITEM_INFO)
-- local subType, equipLoc = 0, ""
-- itemInfoCache[link] = {name, link, quality, level, minLvl, iType, subType, stackSize, equipLoc, texture, vendorPrice}
--elseif strmatch(link, "item:") then
if strmatch(link, "item:") then
itemInfoCache[link] = {GetItemInfo(link)}
end
if itemInfoCache[link] and #itemInfoCache[link] == 0 then itemInfoCache[link] = nil end
end
if not itemInfoCache[link] then return end
return unpack(itemInfoCache[link])
end
--- Attempts to get the itemID from a given itemLink/itemString.
-- @param itemLink The link or itemString for the item.
-- @param ignoreGemID If true, will not attempt to get the equivalent id for the item (ie for old gems where there are multiple ids for a single item).
-- @return Returns the itemID as the first parameter. On error, will return nil as the first parameter and an error message as the second.
function TSMAPI:GetItemID(itemLink)
if not itemLink or type(itemLink) ~= "string" then return nil, "invalid args" end
local test = select(2, strsplit(":", itemLink))
if not test then return nil, "invalid link" end
local s, e = strfind(test, "[0-9]+")
if not (s and e) then return nil, "not an itemLink" end
local itemID = tonumber(strsub(test, s, e))
if not itemID then return nil, "invalid number" end
return itemID
end
-- check if an item is soulbound or not
local function GetTooltipCharges(tooltip)
for id=1, tooltip:NumLines() do
local text = _G["TSMSoulboundScanTooltipTextLeft" .. id]
if text and text:GetText() then
local maxCharges = strmatch(text:GetText(), "^([0-9]+) Charges?$")
if maxCharges then
return maxCharges
end
end
end
end
local scanTooltip
local resultsCache = {lastClear=GetTime()}
function TSMAPI:IsSoulbound(bag, slot)
if GetTime() - resultsCache.lastClear > 0.5 then
resultsCache = {lastClear=GetTime()}
end
if not scanTooltip then
scanTooltip = CreateFrame("GameTooltip", "TSMSoulboundScanTooltip", UIParent, "GameTooltipTemplate")
scanTooltip:SetOwner(UIParent, "ANCHOR_NONE")
end
scanTooltip:ClearLines()
local slotID
if type(bag) == "string" then
if strfind(bag, "battlepet") then return end
slotID = bag
scanTooltip:SetHyperlink(slotID)
elseif bag and slot then
slotID = bag.."@"..slot
local itemID = GetContainerItemID(bag, slot)
local maxCharges
if itemID then
scanTooltip:SetHyperlink("item:"..itemID)
maxCharges = GetTooltipCharges(scanTooltip)
end
scanTooltip:SetBagItem(bag, slot)
if maxCharges then
if GetTooltipCharges(scanTooltip) ~= maxCharges then
resultsCache[slotID] = true
return resultsCache[slotID]
end
end
else
return
end
if resultsCache[slotID] ~= nil then return resultsCache[slotID] end
resultsCache[slotID] = false
for id=1, scanTooltip:NumLines() do
local text = _G["TSMSoulboundScanTooltipTextLeft" .. id]
if text and ((text:GetText() == ITEM_BIND_ON_PICKUP and id < 4) or text:GetText() == ITEM_SOULBOUND or text:GetText() == ITEM_BIND_QUEST) then
resultsCache[slotID] = true
break
end
end
return resultsCache[slotID]
end

21
TradeSkillMaster/LICENSE.txt

@ -0,0 +1,21 @@
All rights are reserved unless explicitly stated below. The "license
holder" is the manager of this project, Sapu94 (sapu94@gmail.com).
Exceptions:
1) The use of this addon in accordance with all applicable terms set by
Blizzard Entertainment for addon use and game play is permitted.
2) Modifications for personal use or submission to license holder are
permitted. Modified versions of the works, derivative works, modified
sections of the works, and instructions for how to modify the works are
all prohibited unless the express consent of the license holder is
granted.
Comments:
1) Permission to use sections of the works in your own work is very
likely to be granted upon contacting the license holder.
2) The right to distribute the works is reserved by the license holder.
In no way or form may a person other than the license holder distribute
the works.
3) Please contact the license holder if you have any questions at all
regarding this license at the following email address: sapu94@gmail.com

12
TradeSkillMaster/Libs/AccurateTime/!AccurateTime.toc

@ -0,0 +1,12 @@
## Interface: 30300
## Title: AccurateTime
## Notes: Provides accurate, millisecond-level timing.
## Author: Sapu94
## Version: v1.6
## X-Curse-Packaged-Version: v1.6
## X-Curse-Project-Name: AccurateTime
## X-Curse-Project-ID: accuratetime
## X-Curse-Repository-ID: wow/accuratetime/mainline
AccurateTime.lua

153
TradeSkillMaster/Libs/AccurateTime/AccurateTime.lua

@ -0,0 +1,153 @@
--[[
This library is intended to fix the shortfalls of using debugprofilestop() for
getting accurate sub-second timing in addons. Specifically, this library aims
to prevent any conflicts that may arrise with multiple addons using
debugprofilestart and debugprofilestop. While perfect accuracy is not
guarenteed due to the potential for an addon to load before this library and
use the original debugprofilestart/debugprofilestop functions, this library
provides a best-effort means of correcting any issues if this is the case.
The best solution is for addons to NOT use debugprofilestart() and to NOT store
a local reference to debugprofilestop(), even if they aren't using this library
directly.
-------------------------------------------------------------------------------
AccurateTime is hereby placed in the Public Domain
See the wowace page for usage and documentation.
Author: Sapu94 (sapu94@gmail.com)
Website: http://www.wowace.com/addons/accuratetime/
--]]
local _G = _G
local AT_VERSION = 4
-- Check if we're already loaded
-- If this is a newer version, remove the old hooks and we'll re-hook
if _G.AccurateTime then
if _G.AccurateTime.version > AT_VERSION then
-- newer (or same) version already loaded - abort
return
end
-- undo hook so we can re-hook
debugprofilestart = _G.AccurateTime._debugprofilestart
debugprofilestop = _G.AccurateTime._debugprofilestop
else
-- reset timer in the back-end so we avoid potential (unlikely) overflows
-- only do this the first time we're loaded
debugprofilestart()
end
-- setup global library reference
_G.AccurateTime = {}
AccurateTime = _G.AccurateTime
AccurateTime.version = AT_VERSION
-- Store original functions.
-- debugprofilestart should never be called, but we'll store it just in case.
AccurateTime._debugprofilestop = debugprofilestop
AccurateTime._debugprofilestart = debugprofilestart
-- key to use for direct, non-library calls to
-- debugprofilestart/debugprofilestop
AccurateTime.DEFAULT_KEY = AccurateTime.DEFAULT_KEY or {}
-- other internal variables
AccurateTime._errorTime = AccurateTime._errorTime or 0
AccurateTime._timers = AccurateTime._timers or {}
-- Gets the current time in milliseconds. Will be directly from the original
-- debugprofilestop() with any error we've detected added in. This error would
-- come solely from an addon calling the unhooked debugprofilestart().
function AccurateTime:GetAbsTime()
return AccurateTime._debugprofilestop() + AccurateTime._errorTime
end
-- It is up to the caller to ensure the key they are using is unique.
-- Using table reference or description strings is preferable.
-- If no key is specified, a unique key will be created and returned.
-- If the timer is already running, restart it.
-- Usage: local key = AccurateTime:GetTimer([key])
function AccurateTime:StartTimer(key)
key = key or {}
AccurateTime._timers[key] = AccurateTime._timers[key] or AccurateTime:GetAbsTime()
return key
end
-- gets the current value of a timer
-- Usage: local value = AccurateTime:GetTimer(key[, silent])
function AccurateTime:GetTimer(key, silent)
assert(key, "No key specified.")
if silent and not AccurateTime._timers[key] then return end
assert(AccurateTime._timers[key], "No timer currently running for the given key.")
return AccurateTime:GetAbsTime() - AccurateTime._timers[key]
end
-- Removes a timer and returns its current value.
-- Usage: local value = AccurateTime:StopTimer(key)
function AccurateTime:StopTimer(key)
if key == AccurateTime.DEFAULT_KEY and not AccurateTime._timers[key] then
-- Don't assert if somebody calls debugprofilestop() without starting
-- This is a best-effort attempt to give them an accurate time
return AccurateTime:GetAbsTime()
end
assert(key, "No key specified.")
assert(AccurateTime._timers[key], "No timer currently running for the given key.")
local value = AccurateTime:GetTimer(key)
AccurateTime._timers[key] = nil
return value
end
-- apply hooks
debugprofilestart = function() AccurateTime:StartTimer(AccurateTime.DEFAULT_KEY) end
debugprofilestop = function() return AccurateTime:StopTimer(AccurateTime.DEFAULT_KEY) end
-- Create an OnUpdate script to detect and attempt to correct other addons
-- which use the original (non-hooked) debugprofilestart(). This should in
-- theory never happen, but we'll do a best-effort correction if it does.
local function OnUpdate(self)
local absTime = AccurateTime:GetAbsTime()
if absTime < self.lastUpdateTime then
-- debugprofilestart() was called and the back-end timer was reset
-- Estimate what the absolute time should be using GetTime() (converted
-- to ms) and add it to AccurateTime._errorTime.
local realAbsTime = self.lastUpdateAbsTime + (GetTime() - self.lastUpdateTime) * 1000
AccurateTime._errorTime = AccurateTime._errorTime + (realAbsTime - absTime)
end
self.lastUpdateAbsTime = absTime
self.lastUpdateTime = GetTime()
end
if not AccurateTime._frame then
-- create frame just once
AccurateTime._frame = CreateFrame("Frame")
AccurateTime._frame.lastUpdateTime = GetTime()
AccurateTime._frame.lastUpdateAbsTime = 0
end
-- upgrade the frame
AccurateTime._frame:SetScript("OnUpdate", OnUpdate)
--[===[@debug@
function AccurateTimeTest()
local start = debugprofilestop()
for i=1, 10000000 do
end
print("loop", debugprofilestop()-start)
start = debugprofilestop()
for i=1, 10000000 do
debugprofilestop()
end
print("overriden", debugprofilestop()-start)
start = debugprofilestop()
for i=1, 10000000 do
AccurateTime._debugprofilestop()
end
print("raw", debugprofilestop()-start)
end
--@end-debug@]===]

674
TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.lua

@ -0,0 +1,674 @@
--- **AceAddon-3.0** provides a template for creating addon objects.
-- It'll provide you with a set of callback functions that allow you to simplify the loading
-- process of your addon.\\
-- Callbacks provided are:\\
-- * **OnInitialize**, which is called directly after the addon is fully loaded.
-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present.
-- * **OnDisable**, which is only called when your addon is manually being disabled.
-- @usage
-- -- A small (but complete) addon, that doesn't do anything,
-- -- but shows usage of the callbacks.
-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
--
-- function MyAddon:OnInitialize()
-- -- do init tasks here, like loading the Saved Variables,
-- -- or setting up slash commands.
-- end
--
-- function MyAddon:OnEnable()
-- -- Do more initialization here, that really enables the use of your addon.
-- -- Register Events, Hook functions, Create Frames, Get information from
-- -- the game that wasn't available in OnInitialize
-- end
--
-- function MyAddon:OnDisable()
-- -- Unhook, Unregister Events, Hide frames that you created.
-- -- You would probably only use an OnDisable if you want to
-- -- build a "standby" mode, or be able to toggle modules on/off.
-- end
-- @class file
-- @name AceAddon-3.0.lua
-- @release $Id$
local MAJOR, MINOR = "AceAddon-3.0", 12
local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceAddon then return end -- No Upgrade needed.
AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame
AceAddon.addons = AceAddon.addons or {} -- addons in general
AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon.
AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized
AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled
AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon
-- Lua APIs
local tinsert, tconcat, tremove = table.insert, table.concat, table.remove
local fmt, tostring = string.format, tostring
local select, pairs, next, type, unpack = select, pairs, next, type, unpack
local loadstring, assert, error = loadstring, assert, error
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler
--[[
xpcall safecall implementation
]]
local xpcall = xpcall
local function errorhandler(err)
return geterrorhandler()(err)
end
local function CreateDispatcher(argCount)
local code = [[
local xpcall, eh = ...
local method, ARGS
local function call() return method(ARGS) end
local function dispatch(func, ...)
method = func
if not method then return end
ARGS = ...
return xpcall(call, eh)
end
return dispatch
]]
local ARGS = {}
for i = 1, argCount do ARGS[i] = "arg"..i end
code = code:gsub("ARGS", tconcat(ARGS, ", "))
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
local dispatcher = CreateDispatcher(argCount)
rawset(self, argCount, dispatcher)
return dispatcher
end})
Dispatchers[0] = function(func)
return xpcall(func, errorhandler)
end
local function safecall(func, ...)
-- we check to see if the func is passed is actually a function here and don't error when it isn't
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
-- present execution should continue without hinderance
if type(func) == "function" then
return Dispatchers[select('#', ...)](func, ...)
end
end
-- local functions that will be implemented further down
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
-- used in the addon metatable
local function addontostring( self ) return self.name end
-- Check if the addon is queued for initialization
local function queuedForInitialization(addon)
for i = 1, #AceAddon.initializequeue do
if AceAddon.initializequeue[i] == addon then
return true
end
end
return false
end
--- Create a new AceAddon-3.0 addon.
-- Any libraries you specified will be embeded, and the addon will be scheduled for
-- its OnInitialize and OnEnable callbacks.
-- The final addon object, with all libraries embeded, will be returned.
-- @paramsig [object ,]name[, lib, ...]
-- @param object Table to use as a base for the addon (optional)
-- @param name Name of the addon object to create
-- @param lib List of libraries to embed into the addon
-- @usage
-- -- Create a simple addon object
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0")
--
-- -- Create a Addon object based on the table of a frame
-- local MyFrame = CreateFrame("Frame")
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0")
function AceAddon:NewAddon(objectorname, ...)
local object,name
local i=1
if type(objectorname)=="table" then
object=objectorname
name=...
i=2
else
name=objectorname
end
if type(name)~="string" then
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
end
if self.addons[name] then
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2)
end
object = object or {}
object.name = name
local addonmeta = {}
local oldmeta = getmetatable(object)
if oldmeta then
for k, v in pairs(oldmeta) do addonmeta[k] = v end
end
addonmeta.__tostring = addontostring
setmetatable( object, addonmeta )
self.addons[name] = object
object.modules = {}
object.orderedModules = {}
object.defaultModuleLibraries = {}
Embed( object ) -- embed NewModule, GetModule methods
self:EmbedLibraries(object, select(i,...))
-- add to queue of addons to be initialized upon ADDON_LOADED
tinsert(self.initializequeue, object)
return object
end
--- Get the addon object by its name from the internal AceAddon registry.
-- Throws an error if the addon object cannot be found (except if silent is set).
-- @param name unique name of the addon object
-- @param silent if true, the addon is optional, silently return nil if its not found
-- @usage
-- -- Get the Addon
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
function AceAddon:GetAddon(name, silent)
if not silent and not self.addons[name] then
error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2)
end
return self.addons[name]
end
-- - Embed a list of libraries into the specified addon.
-- This function will try to embed all of the listed libraries into the addon
-- and error if a single one fails.
--
-- **Note:** This function is for internal use by :NewAddon/:NewModule
-- @paramsig addon, [lib, ...]
-- @param addon addon object to embed the libs in
-- @param lib List of libraries to embed into the addon
function AceAddon:EmbedLibraries(addon, ...)
for i=1,select("#", ... ) do
local libname = select(i, ...)
self:EmbedLibrary(addon, libname, false, 4)
end
end
-- - Embed a library into the addon object.
-- This function will check if the specified library is registered with LibStub
-- and if it has a :Embed function to call. It'll error if any of those conditions
-- fails.
--
-- **Note:** This function is for internal use by :EmbedLibraries
-- @paramsig addon, libname[, silent[, offset]]
-- @param addon addon object to embed the library in
-- @param libname name of the library to embed
-- @param silent marks an embed to fail silently if the library doesn't exist (optional)
-- @param offset will push the error messages back to said offset, defaults to 2 (optional)
function AceAddon:EmbedLibrary(addon, libname, silent, offset)
local lib = LibStub:GetLibrary(libname, true)
if not lib and not silent then
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2)
elseif lib and type(lib.Embed) == "function" then
lib:Embed(addon)
tinsert(self.embeds[addon], libname)
return true
elseif lib then
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2)
end
end
--- Return the specified module from an addon object.
-- Throws an error if the addon object cannot be found (except if silent is set)
-- @name //addon//:GetModule
-- @paramsig name[, silent]
-- @param name unique name of the module
-- @param silent if true, the module is optional, silently return nil if its not found (optional)
-- @usage
-- -- Get the Addon
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- -- Get the Module
-- MyModule = MyAddon:GetModule("MyModule")
function GetModule(self, name, silent)
if not self.modules[name] and not silent then
error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2)
end
return self.modules[name]
end
local function IsModuleTrue(self) return true end
--- Create a new module for the addon.
-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\
-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as
-- an addon object.
-- @name //addon//:NewModule
-- @paramsig name[, prototype|lib[, lib, ...]]
-- @param name unique name of the module
-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional)
-- @param lib List of libraries to embed into the addon
-- @usage
-- -- Create a module with some embeded libraries
-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0")
--
-- -- Create a module with a prototype
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
function NewModule(self, name, prototype, ...)
if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end
if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end
-- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well.
-- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is.
local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name))
module.IsModule = IsModuleTrue
module:SetEnabledState(self.defaultModuleState)
module.moduleName = name
if type(prototype) == "string" then
AceAddon:EmbedLibraries(module, prototype, ...)
else
AceAddon:EmbedLibraries(module, ...)
end
AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries))
if not prototype or type(prototype) == "string" then
prototype = self.defaultModulePrototype or nil
end
if type(prototype) == "table" then
local mt = getmetatable(module)
mt.__index = prototype
setmetatable(module, mt) -- More of a Base class type feel.
end
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
self.modules[name] = module
tinsert(self.orderedModules, module)
return module
end
--- Returns the real name of the addon or module, without any prefix.
-- @name //addon//:GetName
-- @paramsig
-- @usage
-- print(MyAddon:GetName())
-- -- prints "MyAddon"
function GetName(self)
return self.moduleName or self.name
end
--- Enables the Addon, if possible, return true or false depending on success.
-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback
-- and enabling all modules of the addon (unless explicitly disabled).\\
-- :Enable() also sets the internal `enableState` variable to true
-- @name //addon//:Enable
-- @paramsig
-- @usage
-- -- Enable MyModule
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyModule = MyAddon:GetModule("MyModule")
-- MyModule:Enable()
function Enable(self)
self:SetEnabledState(true)
-- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still
-- it'll be enabled after the init process
if not queuedForInitialization(self) then
return AceAddon:EnableAddon(self)
end
end
--- Disables the Addon, if possible, return true or false depending on success.
-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback
-- and disabling all modules of the addon.\\
-- :Disable() also sets the internal `enableState` variable to false
-- @name //addon//:Disable
-- @paramsig
-- @usage
-- -- Disable MyAddon
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyAddon:Disable()
function Disable(self)
self:SetEnabledState(false)
return AceAddon:DisableAddon(self)
end
--- Enables the Module, if possible, return true or false depending on success.
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object.
-- @name //addon//:EnableModule
-- @paramsig name
-- @usage
-- -- Enable MyModule using :GetModule
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyModule = MyAddon:GetModule("MyModule")
-- MyModule:Enable()
--
-- -- Enable MyModule using the short-hand
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyAddon:EnableModule("MyModule")
function EnableModule(self, name)
local module = self:GetModule( name )
return module:Enable()
end
--- Disables the Module, if possible, return true or false depending on success.
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object.
-- @name //addon//:DisableModule
-- @paramsig name
-- @usage
-- -- Disable MyModule using :GetModule
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyModule = MyAddon:GetModule("MyModule")
-- MyModule:Disable()
--
-- -- Disable MyModule using the short-hand
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyAddon:DisableModule("MyModule")
function DisableModule(self, name)
local module = self:GetModule( name )
return module:Disable()
end
--- Set the default libraries to be mixed into all modules created by this object.
-- Note that you can only change the default module libraries before any module is created.
-- @name //addon//:SetDefaultModuleLibraries
-- @paramsig lib[, lib, ...]
-- @param lib List of libraries to embed into the addon
-- @usage
-- -- Create the addon object
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
-- -- Configure default libraries for modules (all modules need AceEvent-3.0)
-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0")
-- -- Create a module
-- MyModule = MyAddon:NewModule("MyModule")
function SetDefaultModuleLibraries(self, ...)
if next(self.modules) then
error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2)
end
self.defaultModuleLibraries = {...}
end
--- Set the default state in which new modules are being created.
-- Note that you can only change the default state before any module is created.
-- @name //addon//:SetDefaultModuleState
-- @paramsig state
-- @param state Default state for new modules, true for enabled, false for disabled
-- @usage
-- -- Create the addon object
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
-- -- Set the default state to "disabled"
-- MyAddon:SetDefaultModuleState(false)
-- -- Create a module and explicilty enable it
-- MyModule = MyAddon:NewModule("MyModule")
-- MyModule:Enable()
function SetDefaultModuleState(self, state)
if next(self.modules) then
error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2)
end
self.defaultModuleState = state
end
--- Set the default prototype to use for new modules on creation.
-- Note that you can only change the default prototype before any module is created.
-- @name //addon//:SetDefaultModulePrototype
-- @paramsig prototype
-- @param prototype Default prototype for the new modules (table)
-- @usage
-- -- Define a prototype
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
-- -- Set the default prototype
-- MyAddon:SetDefaultModulePrototype(prototype)
-- -- Create a module and explicitly Enable it
-- MyModule = MyAddon:NewModule("MyModule")
-- MyModule:Enable()
-- -- should print "OnEnable called!" now
-- @see NewModule
function SetDefaultModulePrototype(self, prototype)
if next(self.modules) then
error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2)
end
if type(prototype) ~= "table" then
error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2)
end
self.defaultModulePrototype = prototype
end
--- Set the state of an addon or module
-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize.
-- @name //addon//:SetEnabledState
-- @paramsig state
-- @param state the state of an addon or module (enabled=true, disabled=false)
function SetEnabledState(self, state)
self.enabledState = state
end
--- Return an iterator of all modules associated to the addon.
-- @name //addon//:IterateModules
-- @paramsig
-- @usage
-- -- Enable all modules
-- for name, module in MyAddon:IterateModules() do
-- module:Enable()
-- end
local function IterateModules(self) return pairs(self.modules) end
-- Returns an iterator of all embeds in the addon
-- @name //addon//:IterateEmbeds
-- @paramsig
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end
--- Query the enabledState of an addon.
-- @name //addon//:IsEnabled
-- @paramsig
-- @usage
-- if MyAddon:IsEnabled() then
-- MyAddon:Disable()
-- end
local function IsEnabled(self) return self.enabledState end
local mixins = {
NewModule = NewModule,
GetModule = GetModule,
Enable = Enable,
Disable = Disable,
EnableModule = EnableModule,
DisableModule = DisableModule,
IsEnabled = IsEnabled,
SetDefaultModuleLibraries = SetDefaultModuleLibraries,
SetDefaultModuleState = SetDefaultModuleState,
SetDefaultModulePrototype = SetDefaultModulePrototype,
SetEnabledState = SetEnabledState,
IterateModules = IterateModules,
IterateEmbeds = IterateEmbeds,
GetName = GetName,
}
local function IsModule(self) return false end
local pmixins = {
defaultModuleState = true,
enabledState = true,
IsModule = IsModule,
}
-- Embed( target )
-- target (object) - target object to embed aceaddon in
--
-- this is a local function specifically since it's meant to be only called internally
function Embed(target, skipPMixins)
for k, v in pairs(mixins) do
target[k] = v
end
if not skipPMixins then
for k, v in pairs(pmixins) do
target[k] = target[k] or v
end
end
end
-- - Initialize the addon after creation.
-- This function is only used internally during the ADDON_LOADED event
-- It will call the **OnInitialize** function on the addon object (if present),
-- and the **OnEmbedInitialize** function on all embeded libraries.
--
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- @param addon addon object to intialize
function AceAddon:InitializeAddon(addon)
safecall(addon.OnInitialize, addon)
local embeds = self.embeds[addon]
for i = 1, #embeds do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
end
-- we don't call InitializeAddon on modules specifically, this is handled
-- from the event handler and only done _once_
end
-- - Enable the addon after creation.
-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED,
-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons.
-- It will call the **OnEnable** function on the addon object (if present),
-- and the **OnEmbedEnable** function on all embeded libraries.\\
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled.
--
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- Use :Enable on the addon itself instead.
-- @param addon addon object to enable
function AceAddon:EnableAddon(addon)
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
if self.statuses[addon.name] or not addon.enabledState then return false end
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable.
self.statuses[addon.name] = true
safecall(addon.OnEnable, addon)
-- make sure we're still enabled before continueing
if self.statuses[addon.name] then
local embeds = self.embeds[addon]
for i = 1, #embeds do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
end
-- enable possible modules.
local modules = addon.orderedModules
for i = 1, #modules do
self:EnableAddon(modules[i])
end
end
return self.statuses[addon.name] -- return true if we're disabled
end
-- - Disable the addon
-- Note: This function is only used internally.
-- It will call the **OnDisable** function on the addon object (if present),
-- and the **OnEmbedDisable** function on all embeded libraries.\\
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled.
--
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- Use :Disable on the addon itself instead.
-- @param addon addon object to enable
function AceAddon:DisableAddon(addon)
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
if not self.statuses[addon.name] then return false end
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable.
self.statuses[addon.name] = false
safecall( addon.OnDisable, addon )
-- make sure we're still disabling...
if not self.statuses[addon.name] then
local embeds = self.embeds[addon]
for i = 1, #embeds do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
end
-- disable possible modules.
local modules = addon.orderedModules
for i = 1, #modules do
self:DisableAddon(modules[i])
end
end
return not self.statuses[addon.name] -- return true if we're disabled
end
--- Get an iterator over all registered addons.
-- @usage
-- -- Print a list of all installed AceAddon's
-- for name, addon in AceAddon:IterateAddons() do
-- print("Addon: " .. name)
-- end
function AceAddon:IterateAddons() return pairs(self.addons) end
--- Get an iterator over the internal status registry.
-- @usage
-- -- Print a list of all enabled addons
-- for name, status in AceAddon:IterateAddonStatus() do
-- if status then
-- print("EnabledAddon: " .. name)
-- end
-- end
function AceAddon:IterateAddonStatus() return pairs(self.statuses) end
-- Following Iterators are deprecated, and their addon specific versions should be used
-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon)
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end
-- Event Handling
local function onEvent(this, event, arg1)
-- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up
if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then
-- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration
while(#AceAddon.initializequeue > 0) do
local addon = tremove(AceAddon.initializequeue, 1)
-- this might be an issue with recursion - TODO: validate
if event == "ADDON_LOADED" then addon.baseName = arg1 end
AceAddon:InitializeAddon(addon)
tinsert(AceAddon.enablequeue, addon)
end
if IsLoggedIn() then
while(#AceAddon.enablequeue > 0) do
local addon = tremove(AceAddon.enablequeue, 1)
AceAddon:EnableAddon(addon)
end
end
end
end
AceAddon.frame:RegisterEvent("ADDON_LOADED")
AceAddon.frame:RegisterEvent("PLAYER_LOGIN")
AceAddon.frame:SetScript("OnEvent", onEvent)
-- upgrade embeded
for name, addon in pairs(AceAddon.addons) do
Embed(addon, true)
end
-- 2010-10-27 nevcairiel - add new "orderedModules" table
if oldminor and oldminor < 10 then
for name, addon in pairs(AceAddon.addons) do
addon.orderedModules = {}
for module_name, module in pairs(addon.modules) do
tinsert(addon.orderedModules, module)
end
end
end

4
TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceAddon-3.0.lua"/>
</Ui>

308
TradeSkillMaster/Libs/AceComm-3.0/AceComm-3.0.lua

@ -0,0 +1,308 @@
--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
--
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceComm.
-- @class file
-- @name AceComm-3.0
-- @release $Id$
--[[ AceComm-3.0
TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
]]
local CallbackHandler = LibStub("CallbackHandler-1.0")
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
local MAJOR, MINOR = "AceComm-3.0", 12
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceComm then return end
-- Lua APIs
local type, next, pairs, tostring = type, next, pairs, tostring
local strsub, strfind = string.sub, string.find
local tinsert, tconcat = table.insert, table.concat
local error, assert = error, assert
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler
AceComm.embeds = AceComm.embeds or {}
-- for my sanity and yours, let's give the message type bytes some names
local MSG_MULTI_FIRST = "\001"
local MSG_MULTI_NEXT = "\002"
local MSG_MULTI_LAST = "\003"
AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix"
AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst"
-- the multipart message spool: indexed by a combination of sender+distribution+
AceComm.multipart_spool = AceComm.multipart_spool or {}
--- Register for Addon Traffic on a specified prefix
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
function AceComm:RegisterComm(prefix, method)
if method == nil then
method = "OnCommReceived"
end
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
end
local warnedPrefix=false
--- Send a message over the Addon Channel
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
-- @param text Data to send, nils (\000) not allowed. Any length.
-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
-- @param target Destination for some distributions; see SendAddonMessage API
-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
if not( type(prefix)=="string" and
type(text)=="string" and
type(distribution)=="string" and
(target==nil or type(target)=="string" or type(target)=="number") and
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
) then
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
end
if strfind(prefix, "[\001-\009]") then
if strfind(prefix, "[\001-\003]") then
error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2)
elseif not warnedPrefix then
-- I have some ideas about future extensions that require more control characters /mikk, 20090808
geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension")
warnedPrefix = true
end
end
local textlen = #text
local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message
local queueName = prefix..distribution..(target or "")
local ctlCallback = nil
if callbackFn then
ctlCallback = function(sent)
return callbackFn(callbackArg, sent, textlen)
end
end
if textlen <= maxtextlen then
-- fits all in one message
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
else
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix
-- first part
local chunk = strsub(text, 1, maxtextlen)
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen)
-- continuation
local pos = 1+maxtextlen
local prefix2 = prefix..MSG_MULTI_NEXT
while pos+maxtextlen <= textlen do
chunk = strsub(text, pos, pos+maxtextlen-1)
CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
pos = pos + maxtextlen
end
-- final part
chunk = strsub(text, pos)
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen)
end
end
----------------------------------------
-- Message receiving
----------------------------------------
do
local compost = setmetatable({}, {__mode = "k"})
local function new()
local t = next(compost)
if t then
compost[t]=nil
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
t[i]=nil
end
return t
end
return {}
end
local function lostdatawarning(prefix,sender,where)
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
end
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
local spool = AceComm.multipart_spool
--[[
if spool[key] then
lostdatawarning(prefix,sender,"First")
-- continue and overwrite
end
--]]
spool[key] = message -- plain string for now
end
function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
local spool = AceComm.multipart_spool
local olddata = spool[key]
if not olddata then
--lostdatawarning(prefix,sender,"Next")
return
end
if type(olddata)~="table" then
-- ... but what we have is not a table. So make it one. (Pull a composted one if available)
local t = new()
t[1] = olddata -- add old data as first string
t[2] = message -- and new message as second string
spool[key] = t -- and put the table in the spool instead of the old string
else
tinsert(olddata, message)
end
end
function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
local spool = AceComm.multipart_spool
local olddata = spool[key]
if not olddata then
--lostdatawarning(prefix,sender,"End")
return
end
spool[key] = nil
if type(olddata) == "table" then
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
tinsert(olddata, message)
AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
compost[olddata] = true
else
-- if we've only received a "first", the spooled data will still only be a string
AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
end
end
end
----------------------------------------
-- Embed CallbackHandler
----------------------------------------
if not AceComm.callbacks then
-- ensure that 'prefix to watch' table is consistent with registered
-- callbacks
AceComm.__prefixes = {}
AceComm.callbacks = CallbackHandler:New(AceComm,
"_RegisterComm",
"UnregisterComm",
"UnregisterAllComm")
end
function AceComm.callbacks:OnUsed(target, prefix)
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst"
AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix
AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext"
AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix
AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast"
end
function AceComm.callbacks:OnUnused(target, prefix)
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil
AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil
AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil
AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil
AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil
end
local function OnEvent(this, event, ...)
if event == "CHAT_MSG_ADDON" then
local prefix,message,distribution,sender = ...
local reassemblername = AceComm.multipart_reassemblers[prefix]
if reassemblername then
-- multipart: reassemble
local aceCommReassemblerFunc = AceComm[reassemblername]
local origprefix = AceComm.multipart_origprefixes[prefix]
aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender)
else
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
AceComm.callbacks:Fire(prefix, message, distribution, sender)
end
else
assert(false, "Received "..tostring(event).." event?!")
end
end
AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
AceComm.frame:SetScript("OnEvent", OnEvent)
AceComm.frame:UnregisterAllEvents()
AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
----------------------------------------
-- Base library stuff
----------------------------------------
local mixins = {
"RegisterComm",
"UnregisterComm",
"UnregisterAllComm",
"SendCommMessage",
}
-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceComm-3.0 in
function AceComm:Embed(target)
for k, v in pairs(mixins) do
target[v] = self[v]
end
self.embeds[target] = true
return target
end
function AceComm:OnEmbedDisable(target)
target:UnregisterAllComm()
end
-- Update embeds
for target, v in pairs(AceComm.embeds) do
AceComm:Embed(target)
end

5
TradeSkillMaster/Libs/AceComm-3.0/AceComm-3.0.xml

@ -0,0 +1,5 @@
<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/
..\FrameXML\UI.xsd">
<Script file="ChatThrottleLib.lua"/>
<Script file="AceComm-3.0.lua"/>
</Ui>

523
TradeSkillMaster/Libs/AceComm-3.0/ChatThrottleLib.lua

@ -0,0 +1,523 @@
--
-- ChatThrottleLib by Mikk
--
-- Manages AddOn chat output to keep player from getting kicked off.
--
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
--
-- Priorities get an equal share of available bandwidth when fully loaded.
-- Communication channels are separated on extension+chattype+destination and
-- get round-robinned. (Destination only matters for whispers and channels,
-- obviously)
--
-- Will install hooks for SendChatMessage and SendAddonMessage to measure
-- bandwidth bypassing the library and use less bandwidth itself.
--
--
-- Fully embeddable library. Just copy this file into your addon directory,
-- add it to the .toc, and it's done.
--
-- Can run as a standalone addon also, but, really, just embed it! :-)
--
-- LICENSE: ChatThrottleLib is released into the Public Domain
--
local CTL_VERSION = 24
local _G = _G
if _G.ChatThrottleLib then
if _G.ChatThrottleLib.version >= CTL_VERSION then
-- There's already a newer (or same) version loaded. Buh-bye.
return
elseif not _G.ChatThrottleLib.securelyHooked then
print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!")
-- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
-- ... and if someone has securehooked, they can kiss that goodbye too... >.<
_G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
if _G.ChatThrottleLib.ORIG_SendAddonMessage then
_G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
end
end
_G.ChatThrottleLib.ORIG_SendChatMessage = nil
_G.ChatThrottleLib.ORIG_SendAddonMessage = nil
end
if not _G.ChatThrottleLib then
_G.ChatThrottleLib = {}
end
ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
local ChatThrottleLib = _G.ChatThrottleLib
ChatThrottleLib.version = CTL_VERSION
------------------ TWEAKABLES -----------------
ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value
local setmetatable = setmetatable
local table_remove = table.remove
local tostring = tostring
local GetTime = GetTime
local math_min = math.min
local math_max = math.max
local next = next
local strlen = string.len
local GetFramerate = GetFramerate
local strlower = string.lower
local unpack,type,pairs,wipe = unpack,type,pairs,wipe
local UnitInRaid,GetNumPartyMembers = UnitInRaid,GetNumPartyMembers
-----------------------------------------------------------------------
-- Double-linked ring implementation
local Ring = {}
local RingMeta = { __index = Ring }
function Ring:New()
local ret = {}
setmetatable(ret, RingMeta)
return ret
end
function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position)
if self.pos then
obj.prev = self.pos.prev
obj.prev.next = obj
obj.next = self.pos
obj.next.prev = obj
else
obj.next = obj
obj.prev = obj
self.pos = obj
end
end
function Ring:Remove(obj)
obj.next.prev = obj.prev
obj.prev.next = obj.next
if self.pos == obj then
self.pos = obj.next
if self.pos == obj then
self.pos = nil
end
end
end
-----------------------------------------------------------------------
-- Recycling bin for pipes
-- A pipe is a plain integer-indexed queue of messages
-- Pipes normally live in Rings of pipes (3 rings total, one per priority)
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
local PipeBin = setmetatable({}, {__mode="k"})
local function DelPipe(pipe)
for i = #pipe, 1, -1 do
pipe[i] = nil
end
pipe.prev = nil
pipe.next = nil
PipeBin[pipe] = true
end
local function NewPipe()
local pipe = next(PipeBin)
if pipe then
wipe(pipe)
PipeBin[pipe] = nil
return pipe
end
return {}
end
-----------------------------------------------------------------------
-- Recycling bin for messages
ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
local MsgBin = setmetatable({}, {__mode="k"})
local function DelMsg(msg)
msg[1] = nil
-- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
MsgBin[msg] = true
end
local function NewMsg()
local msg = next(MsgBin)
if msg then
MsgBin[msg] = nil
return msg
end
return {}
end
-----------------------------------------------------------------------
-- ChatThrottleLib:Init
-- Initialize queues, set up frame for OnUpdate, etc
function ChatThrottleLib:Init()
-- Set up queues
if not self.Prio then
self.Prio = {}
self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
end
-- v4: total send counters per priority
for _, Prio in pairs(self.Prio) do
Prio.nTotalSent = Prio.nTotalSent or 0
end
if not self.avail then
self.avail = 0 -- v5
end
if not self.nTotalSent then
self.nTotalSent = 0 -- v5
end
-- Set up a frame to get OnUpdate events
if not self.Frame then
self.Frame = CreateFrame("Frame")
self.Frame:Hide()
end
self.Frame:SetScript("OnUpdate", self.OnUpdate)
self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds
self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
self.OnUpdateDelay = 0
self.LastAvailUpdate = GetTime()
self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
-- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
if not self.securelyHooked then
-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
self.securelyHooked = true
--SendChatMessage
hooksecurefunc("SendChatMessage", function(...)
return ChatThrottleLib.Hook_SendChatMessage(...)
end)
--SendAddonMessage
hooksecurefunc("SendAddonMessage", function(...)
return ChatThrottleLib.Hook_SendAddonMessage(...)
end)
end
self.nBypass = 0
end
-----------------------------------------------------------------------
-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
local bMyTraffic = false
function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
if bMyTraffic then
return
end
local self = ChatThrottleLib
local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
self.avail = self.avail - size
self.nBypass = self.nBypass + size -- just a statistic
end
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
if bMyTraffic then
return
end
local self = ChatThrottleLib
local size = tostring(text or ""):len() + tostring(prefix or ""):len();
size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
self.avail = self.avail - size
self.nBypass = self.nBypass + size -- just a statistic
end
-----------------------------------------------------------------------
-- ChatThrottleLib:UpdateAvail
-- Update self.avail with how much bandwidth is currently available
function ChatThrottleLib:UpdateAvail()
local now = GetTime()
local MAX_CPS = self.MAX_CPS;
local newavail = MAX_CPS * (now - self.LastAvailUpdate)
local avail = self.avail
if now - self.HardThrottlingBeginTime < 5 then
-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
self.bChoking = true
elseif GetFramerate() < self.MIN_FPS then -- GetFramerate call takes ~0.002 secs
avail = math_min(MAX_CPS, avail + newavail*0.5)
self.bChoking = true -- just a statistic
else
avail = math_min(self.BURST, avail + newavail)
self.bChoking = false
end
avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
self.avail = avail
self.LastAvailUpdate = now
return avail
end
-----------------------------------------------------------------------
-- Despooling logic
-- Reminder:
-- - We have 3 Priorities, each containing a "Ring" construct ...
-- - ... made up of N "Pipe"s (1 for each destination/pipename)
-- - and each pipe contains messages
function ChatThrottleLib:Despool(Prio)
local ring = Prio.Ring
while ring.pos and Prio.avail > ring.pos[1].nSize do
local msg = table_remove(ring.pos, 1)
if not ring.pos[1] then -- did we remove last msg in this pipe?
local pipe = Prio.Ring.pos
Prio.Ring:Remove(pipe)
Prio.ByName[pipe.name] = nil
DelPipe(pipe)
else
Prio.Ring.pos = Prio.Ring.pos.next
end
local didSend=false
local lowerDest = strlower(msg[3] or "")
if lowerDest == "raid" and not UnitInRaid("player") then
-- do nothing
elseif lowerDest == "party" and GetNumPartyMembers() == 0 then
-- do nothing
else
Prio.avail = Prio.avail - msg.nSize
bMyTraffic = true
msg.f(unpack(msg, 1, msg.n))
bMyTraffic = false
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
DelMsg(msg)
didSend = true
end
-- notify caller of delivery (even if we didn't send it)
if msg.callbackFn then
msg.callbackFn (msg.callbackArg, didSend)
end
-- USER CALLBACK MAY ERROR
end
end
function ChatThrottleLib.OnEvent(this,event)
-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
local self = ChatThrottleLib
if event == "PLAYER_ENTERING_WORLD" then
self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning
self.avail = 0
end
end
function ChatThrottleLib.OnUpdate(this,delay)
local self = ChatThrottleLib
self.OnUpdateDelay = self.OnUpdateDelay + delay
if self.OnUpdateDelay < 0.08 then
return
end
self.OnUpdateDelay = 0
self:UpdateAvail()
if self.avail < 0 then
return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
end
-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
local n = 0
for prioname,Prio in pairs(self.Prio) do
if Prio.Ring.pos or Prio.avail < 0 then
n = n + 1
end
end
-- Anything queued still?
if n<1 then
-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
for prioname, Prio in pairs(self.Prio) do
self.avail = self.avail + Prio.avail
Prio.avail = 0
end
self.bQueueing = false
self.Frame:Hide()
return
end
-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
local avail = self.avail/n
self.avail = 0
for prioname, Prio in pairs(self.Prio) do
if Prio.Ring.pos or Prio.avail < 0 then
Prio.avail = Prio.avail + avail
if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
self:Despool(Prio)
-- Note: We might not get here if the user-supplied callback function errors out! Take care!
end
end
end
end
-----------------------------------------------------------------------
-- Spooling logic
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
local Prio = self.Prio[prioname]
local pipe = Prio.ByName[pipename]
if not pipe then
self.Frame:Show()
pipe = NewPipe()
pipe.name = pipename
Prio.ByName[pipename] = pipe
Prio.Ring:Add(pipe)
end
pipe[#pipe + 1] = msg
self.bQueueing = true
end
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
if not self or not prio or not prefix or not text or not self.Prio[prio] then
error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
end
if callbackFn and type(callbackFn)~="function" then
error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
end
local nSize = text:len()
if nSize>255 then
error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
end
nSize = nSize + self.MSG_OVERHEAD
-- Check if there's room in the global available bandwidth gauge to send directly
if not self.bQueueing and nSize < self:UpdateAvail() then
self.avail = self.avail - nSize
bMyTraffic = true
_G.SendChatMessage(text, chattype, language, destination)
bMyTraffic = false
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
if callbackFn then
callbackFn (callbackArg, true)
end
-- USER CALLBACK MAY ERROR
return
end
-- Message needs to be queued
local msg = NewMsg()
msg.f = _G.SendChatMessage
msg[1] = text
msg[2] = chattype or "SAY"
msg[3] = language
msg[4] = destination
msg.n = 4
msg.nSize = nSize
msg.callbackFn = callbackFn
msg.callbackArg = callbackArg
self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
end
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
end
if callbackFn and type(callbackFn)~="function" then
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
end
local nSize = prefix:len() + 1 + text:len();
if nSize>255 then
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
end
nSize = nSize + self.MSG_OVERHEAD;
-- Check if there's room in the global available bandwidth gauge to send directly
if not self.bQueueing and nSize < self:UpdateAvail() then
self.avail = self.avail - nSize
bMyTraffic = true
_G.SendAddonMessage(prefix, text, chattype, target)
bMyTraffic = false
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
if callbackFn then
callbackFn (callbackArg, true)
end
-- USER CALLBACK MAY ERROR
return
end
-- Message needs to be queued
local msg = NewMsg()
msg.f = _G.SendAddonMessage
msg[1] = prefix
msg[2] = text
msg[3] = chattype
msg[4] = target
msg.n = (target~=nil) and 4 or 3;
msg.nSize = nSize
msg.callbackFn = callbackFn
msg.callbackArg = callbackArg
self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
end
-----------------------------------------------------------------------
-- Get the ball rolling!
ChatThrottleLib:Init()
--[[ WoWBench debugging snippet
if(WOWB_VER) then
local function SayTimer()
print("SAY: "..GetTime().." "..arg1)
end
ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
end
]]

58
TradeSkillMaster/Libs/AceConfig-3.0/AceConfig-3.0.lua

@ -0,0 +1,58 @@
--- AceConfig-3.0 wrapper library.
-- Provides an API to register an options table with the config registry,
-- as well as associate it with a slash command.
-- @class file
-- @name AceConfig-3.0
-- @release $Id: AceConfig-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
--[[
AceConfig-3.0
Very light wrapper library that combines all the AceConfig subcomponents into one more easily used whole.
]]
local cfgreg = LibStub("AceConfigRegistry-3.0")
local cfgcmd = LibStub("AceConfigCmd-3.0")
local MAJOR, MINOR = "AceConfig-3.0", 3
local AceConfig = LibStub:NewLibrary(MAJOR, MINOR)
if not AceConfig then return end
--TODO: local cfgdlg = LibStub("AceConfigDialog-3.0", true)
--TODO: local cfgdrp = LibStub("AceConfigDropdown-3.0", true)
-- Lua APIs
local pcall, error, type, pairs = pcall, error, type, pairs
-- -------------------------------------------------------------------
-- :RegisterOptionsTable(appName, options, slashcmd, persist)
--
-- - appName - (string) application name
-- - options - table or function ref, see AceConfigRegistry
-- - slashcmd - slash command (string) or table with commands, or nil to NOT create a slash command
--- Register a option table with the AceConfig registry.
-- You can supply a slash command (or a table of slash commands) to register with AceConfigCmd directly.
-- @paramsig appName, options [, slashcmd]
-- @param appName The application name for the config table.
-- @param options The option table (or a function to generate one on demand). http://www.wowace.com/addons/ace3/pages/ace-config-3-0-options-tables/
-- @param slashcmd A slash command to register for the option table, or a table of slash commands.
-- @usage
-- local AceConfig = LibStub("AceConfig-3.0")
-- AceConfig:RegisterOptionsTable("MyAddon", myOptions, {"/myslash", "/my"})
function AceConfig:RegisterOptionsTable(appName, options, slashcmd)
local ok,msg = pcall(cfgreg.RegisterOptionsTable, self, appName, options)
if not ok then error(msg, 2) end
if slashcmd then
if type(slashcmd) == "table" then
for _,cmd in pairs(slashcmd) do
cfgcmd:CreateChatCommand(cmd, appName)
end
else
cfgcmd:CreateChatCommand(slashcmd, appName)
end
end
end

8
TradeSkillMaster/Libs/AceConfig-3.0/AceConfig-3.0.xml

@ -0,0 +1,8 @@
<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/
..\FrameXML\UI.xsd">
<Include file="AceConfigRegistry-3.0\AceConfigRegistry-3.0.xml"/>
<Include file="AceConfigCmd-3.0\AceConfigCmd-3.0.xml"/>
<Include file="AceConfigDialog-3.0\AceConfigDialog-3.0.xml"/>
<!--<Include file="AceConfigDropdown-3.0\AceConfigDropdown-3.0.xml"/>-->
<Script file="AceConfig-3.0.lua"/>
</Ui>

794
TradeSkillMaster/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua

@ -0,0 +1,794 @@
--- AceConfigCmd-3.0 handles access to an options table through the "command line" interface via the ChatFrames.
-- @class file
-- @name AceConfigCmd-3.0
-- @release $Id: AceConfigCmd-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
--[[
AceConfigCmd-3.0
Handles commandline optionstable access
REQUIRES: AceConsole-3.0 for command registration (loaded on demand)
]]
-- TODO: plugin args
local cfgreg = LibStub("AceConfigRegistry-3.0")
local MAJOR, MINOR = "AceConfigCmd-3.0", 14
local AceConfigCmd = LibStub:NewLibrary(MAJOR, MINOR)
if not AceConfigCmd then return end
AceConfigCmd.commands = AceConfigCmd.commands or {}
local commands = AceConfigCmd.commands
local AceConsole -- LoD
local AceConsoleName = "AceConsole-3.0"
-- Lua APIs
local strsub, strsplit, strlower, strmatch, strtrim = string.sub, string.split, string.lower, string.match, string.trim
local format, tonumber, tostring = string.format, tonumber, tostring
local tsort, tinsert = table.sort, table.insert
local select, pairs, next, type = select, pairs, next, type
local error, assert = error, assert
-- WoW APIs
local _G = _G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub, SELECTED_CHAT_FRAME, DEFAULT_CHAT_FRAME
local L = setmetatable({}, { -- TODO: replace with proper locale
__index = function(self,k) return k end
})
local function print(msg)
(SELECTED_CHAT_FRAME or DEFAULT_CHAT_FRAME):AddMessage(msg)
end
-- constants used by getparam() calls below
local handlertypes = {["table"]=true}
local handlermsg = "expected a table"
local functypes = {["function"]=true, ["string"]=true}
local funcmsg = "expected function or member name"
-- pickfirstset() - picks the first non-nil value and returns it
local function pickfirstset(...)
for i=1,select("#",...) do
if select(i,...)~=nil then
return select(i,...)
end
end
end
-- err() - produce real error() regarding malformed options tables etc
local function err(info,inputpos,msg )
local cmdstr=" "..strsub(info.input, 1, inputpos-1)
error(MAJOR..": /" ..info[0] ..cmdstr ..": "..(msg or "malformed options table"), 2)
end
-- usererr() - produce chatframe message regarding bad slash syntax etc
local function usererr(info,inputpos,msg )
local cmdstr=strsub(info.input, 1, inputpos-1);
print("/" ..info[0] .. " "..cmdstr ..": "..(msg or "malformed options table"))
end
-- callmethod() - call a given named method (e.g. "get", "set") with given arguments
local function callmethod(info, inputpos, tab, methodtype, ...)
local method = info[methodtype]
if not method then
err(info, inputpos, "'"..methodtype.."': not set")
end
info.arg = tab.arg
info.option = tab
info.type = tab.type
if type(method)=="function" then
return method(info, ...)
elseif type(method)=="string" then
if type(info.handler[method])~="function" then
err(info, inputpos, "'"..methodtype.."': '"..method.."' is not a member function of "..tostring(info.handler))
end
return info.handler[method](info.handler, info, ...)
else
assert(false) -- type should have already been checked on read
end
end
-- callfunction() - call a given named function (e.g. "name", "desc") with given arguments
local function callfunction(info, tab, methodtype, ...)
local method = tab[methodtype]
info.arg = tab.arg
info.option = tab
info.type = tab.type
if type(method)=="function" then
return method(info, ...)
else
assert(false) -- type should have already been checked on read
end
end
-- do_final() - do the final step (set/execute) along with validation and confirmation
local function do_final(info, inputpos, tab, methodtype, ...)
if info.validate then
local res = callmethod(info,inputpos,tab,"validate",...)
if type(res)=="string" then
usererr(info, inputpos, "'"..strsub(info.input, inputpos).."' - "..res)
return
end
end
-- console ignores .confirm
callmethod(info,inputpos,tab,methodtype, ...)
end
-- getparam() - used by handle() to retreive and store "handler", "get", "set", etc
local function getparam(info, inputpos, tab, depth, paramname, types, errormsg)
local old,oldat = info[paramname], info[paramname.."_at"]
local val=tab[paramname]
if val~=nil then
if val==false then
val=nil
elseif not types[type(val)] then
err(info, inputpos, "'" .. paramname.. "' - "..errormsg)
end
info[paramname] = val
info[paramname.."_at"] = depth
end
return old,oldat
end
-- iterateargs(tab) - custom iterator that iterates both t.args and t.plugins.*
local dummytable={}
local function iterateargs(tab)
if not tab.plugins then
return pairs(tab.args)
end
local argtabkey,argtab=next(tab.plugins)
local v
return function(_, k)
while argtab do
k,v = next(argtab, k)
if k then return k,v end
if argtab==tab.args then
argtab=nil
else
argtabkey,argtab = next(tab.plugins, argtabkey)
if not argtabkey then
argtab=tab.args
end
end
end
end
end
local function checkhidden(info, inputpos, tab)
if tab.cmdHidden~=nil then
return tab.cmdHidden
end
local hidden = tab.hidden
if type(hidden) == "function" or type(hidden) == "string" then
info.hidden = hidden
hidden = callmethod(info, inputpos, tab, 'hidden')
info.hidden = nil
end
return hidden
end
local function showhelp(info, inputpos, tab, depth, noHead)
if not noHead then
print("|cff33ff99"..info.appName.."|r: Arguments to |cffffff78/"..info[0].."|r "..strsub(info.input,1,inputpos-1)..":")
end
local sortTbl = {} -- [1..n]=name
local refTbl = {} -- [name]=tableref
for k,v in iterateargs(tab) do
if not refTbl[k] then -- a plugin overriding something in .args
tinsert(sortTbl, k)
refTbl[k] = v
end
end
tsort(sortTbl, function(one, two)
local o1 = refTbl[one].order or 100
local o2 = refTbl[two].order or 100
if type(o1) == "function" or type(o1) == "string" then
info.order = o1
info[#info+1] = one
o1 = callmethod(info, inputpos, refTbl[one], "order")
info[#info] = nil
info.order = nil
end
if type(o2) == "function" or type(o1) == "string" then
info.order = o2
info[#info+1] = two
o2 = callmethod(info, inputpos, refTbl[two], "order")
info[#info] = nil
info.order = nil
end
if o1<0 and o2<0 then return o1<o2 end
if o2<0 then return true end
if o1<0 then return false end
if o1==o2 then return tostring(one)<tostring(two) end -- compare names
return o1<o2
end)
for i = 1, #sortTbl do
local k = sortTbl[i]
local v = refTbl[k]
if not checkhidden(info, inputpos, v) then
if v.type ~= "description" and v.type ~= "header" then
-- recursively show all inline groups
local name, desc = v.name, v.desc
if type(name) == "function" then
name = callfunction(info, v, 'name')
end
if type(desc) == "function" then
desc = callfunction(info, v, 'desc')
end
if v.type == "group" and pickfirstset(v.cmdInline, v.inline, false) then
print(" "..(desc or name)..":")
local oldhandler,oldhandler_at = getparam(info, inputpos, v, depth, "handler", handlertypes, handlermsg)
showhelp(info, inputpos, v, depth, true)
info.handler,info.handler_at = oldhandler,oldhandler_at
else
local key = k:gsub(" ", "_")
print(" |cffffff78"..key.."|r - "..(desc or name or ""))
end
end
end
end
end
local function keybindingValidateFunc(text)
if text == nil or text == "NONE" then
return nil
end
text = text:upper()
local shift, ctrl, alt
local modifier
while true do
if text == "-" then
break
end
modifier, text = strsplit('-', text, 2)
if text then
if modifier ~= "SHIFT" and modifier ~= "CTRL" and modifier ~= "ALT" then
return false
end
if modifier == "SHIFT" then
if shift then
return false
end
shift = true
end
if modifier == "CTRL" then
if ctrl then
return false
end
ctrl = true
end
if modifier == "ALT" then
if alt then
return false
end
alt = true
end
else
text = modifier
break
end
end
if text == "" then
return false
end
if not text:find("^F%d+$") and text ~= "CAPSLOCK" and text:len() ~= 1 and (text:byte() < 128 or text:len() > 4) and not _G["KEY_" .. text] then
return false
end
local s = text
if shift then
s = "SHIFT-" .. s
end
if ctrl then
s = "CTRL-" .. s
end
if alt then
s = "ALT-" .. s
end
return s
end
-- handle() - selfrecursing function that processes input->optiontable
-- - depth - starts at 0
-- - retfalse - return false rather than produce error if a match is not found (used by inlined groups)
local function handle(info, inputpos, tab, depth, retfalse)
if not(type(tab)=="table" and type(tab.type)=="string") then err(info,inputpos) end
-------------------------------------------------------------------
-- Grab hold of handler,set,get,func,etc if set (and remember old ones)
-- Note that we do NOT validate if method names are correct at this stage,
-- the handler may change before they're actually used!
local oldhandler,oldhandler_at = getparam(info,inputpos,tab,depth,"handler",handlertypes,handlermsg)
local oldset,oldset_at = getparam(info,inputpos,tab,depth,"set",functypes,funcmsg)
local oldget,oldget_at = getparam(info,inputpos,tab,depth,"get",functypes,funcmsg)
local oldfunc,oldfunc_at = getparam(info,inputpos,tab,depth,"func",functypes,funcmsg)
local oldvalidate,oldvalidate_at = getparam(info,inputpos,tab,depth,"validate",functypes,funcmsg)
--local oldconfirm,oldconfirm_at = getparam(info,inputpos,tab,depth,"confirm",functypes,funcmsg)
-------------------------------------------------------------------
-- Act according to .type of this table
if tab.type=="group" then
------------ group --------------------------------------------
if type(tab.args)~="table" then err(info, inputpos) end
if tab.plugins and type(tab.plugins)~="table" then err(info,inputpos) end
-- grab next arg from input
local _,nextpos,arg = (info.input):find(" *([^ ]+) *", inputpos)
if not arg then
showhelp(info, inputpos, tab, depth)
return
end
nextpos=nextpos+1
-- loop .args and try to find a key with a matching name
for k,v in iterateargs(tab) do
if not(type(k)=="string" and type(v)=="table" and type(v.type)=="string") then err(info,inputpos, "options table child '"..tostring(k).."' is malformed") end
-- is this child an inline group? if so, traverse into it
if v.type=="group" and pickfirstset(v.cmdInline, v.inline, false) then
info[depth+1] = k
if handle(info, inputpos, v, depth+1, true)==false then
info[depth+1] = nil
-- wasn't found in there, but that's ok, we just keep looking down here
else
return -- done, name was found in inline group
end
-- matching name and not a inline group
elseif strlower(arg)==strlower(k:gsub(" ", "_")) then
info[depth+1] = k
return handle(info,nextpos,v,depth+1)
end
end
-- no match
if retfalse then
-- restore old infotable members and return false to indicate failure
info.handler,info.handler_at = oldhandler,oldhandler_at
info.set,info.set_at = oldset,oldset_at
info.get,info.get_at = oldget,oldget_at
info.func,info.func_at = oldfunc,oldfunc_at
info.validate,info.validate_at = oldvalidate,oldvalidate_at
--info.confirm,info.confirm_at = oldconfirm,oldconfirm_at
return false
end
-- couldn't find the command, display error
usererr(info, inputpos, "'"..arg.."' - " .. L["unknown argument"])
return
end
local str = strsub(info.input,inputpos);
if tab.type=="execute" then
------------ execute --------------------------------------------
do_final(info, inputpos, tab, "func")
elseif tab.type=="input" then
------------ input --------------------------------------------
local res = true
if tab.pattern then
if not(type(tab.pattern)=="string") then err(info, inputpos, "'pattern' - expected a string") end
if not strmatch(str, tab.pattern) then
usererr(info, inputpos, "'"..str.."' - " .. L["invalid input"])
return
end
end
do_final(info, inputpos, tab, "set", str)
elseif tab.type=="toggle" then
------------ toggle --------------------------------------------
local b
local str = strtrim(strlower(str))
if str=="" then
b = callmethod(info, inputpos, tab, "get")
if tab.tristate then
--cycle in true, nil, false order
if b then
b = nil
elseif b == nil then
b = false
else
b = true
end
else
b = not b
end
elseif str==L["on"] then
b = true
elseif str==L["off"] then
b = false
elseif tab.tristate and str==L["default"] then
b = nil
else
if tab.tristate then
usererr(info, inputpos, format(L["'%s' - expected 'on', 'off' or 'default', or no argument to toggle."], str))
else
usererr(info, inputpos, format(L["'%s' - expected 'on' or 'off', or no argument to toggle."], str))
end
return
end
do_final(info, inputpos, tab, "set", b)
elseif tab.type=="range" then
------------ range --------------------------------------------
local val = tonumber(str)
if not val then
usererr(info, inputpos, "'"..str.."' - "..L["expected number"])
return
end
if type(info.step)=="number" then
val = val- (val % info.step)
end
if type(info.min)=="number" and val<info.min then
usererr(info, inputpos, val.." - "..format(L["must be equal to or higher than %s"], tostring(info.min)) )
return
end
if type(info.max)=="number" and val>info.max then
usererr(info, inputpos, val.." - "..format(L["must be equal to or lower than %s"], tostring(info.max)) )
return
end
do_final(info, inputpos, tab, "set", val)
elseif tab.type=="select" then
------------ select ------------------------------------
local str = strtrim(strlower(str))
local values = tab.values
if type(values) == "function" or type(values) == "string" then
info.values = values
values = callmethod(info, inputpos, tab, "values")
info.values = nil
end
if str == "" then
local b = callmethod(info, inputpos, tab, "get")
local fmt = "|cffffff78- [%s]|r %s"
local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r"
print(L["Options for |cffffff78"..info[#info].."|r:"])
for k, v in pairs(values) do
if b == k then
print(fmt_sel:format(k, v))
else
print(fmt:format(k, v))
end
end
return
end
local ok
for k,v in pairs(values) do
if strlower(k)==str then
str = k -- overwrite with key (in case of case mismatches)
ok = true
break
end
end
if not ok then
usererr(info, inputpos, "'"..str.."' - "..L["unknown selection"])
return
end
do_final(info, inputpos, tab, "set", str)
elseif tab.type=="multiselect" then
------------ multiselect -------------------------------------------
local str = strtrim(strlower(str))
local values = tab.values
if type(values) == "function" or type(values) == "string" then
info.values = values
values = callmethod(info, inputpos, tab, "values")
info.values = nil
end
if str == "" then
local fmt = "|cffffff78- [%s]|r %s"
local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r"
print(L["Options for |cffffff78"..info[#info].."|r (multiple possible):"])
for k, v in pairs(values) do
if callmethod(info, inputpos, tab, "get", k) then
print(fmt_sel:format(k, v))
else
print(fmt:format(k, v))
end
end
return
end
--build a table of the selections, checking that they exist
--parse for =on =off =default in the process
--table will be key = true for options that should toggle, key = [on|off|default] for options to be set
local sels = {}
for v in str:gmatch("[^ ]+") do
--parse option=on etc
local opt, val = v:match('(.+)=(.+)')
--get option if toggling
if not opt then
opt = v
end
--check that the opt is valid
local ok
for k,v in pairs(values) do
if strlower(k)==opt then
opt = k -- overwrite with key (in case of case mismatches)
ok = true
break
end
end
if not ok then
usererr(info, inputpos, "'"..opt.."' - "..L["unknown selection"])
return
end
--check that if val was supplied it is valid
if val then
if val == L["on"] or val == L["off"] or (tab.tristate and val == L["default"]) then
--val is valid insert it
sels[opt] = val
else
if tab.tristate then
usererr(info, inputpos, format(L["'%s' '%s' - expected 'on', 'off' or 'default', or no argument to toggle."], v, val))
else
usererr(info, inputpos, format(L["'%s' '%s' - expected 'on' or 'off', or no argument to toggle."], v, val))
end
return
end
else
-- no val supplied, toggle
sels[opt] = true
end
end
for opt, val in pairs(sels) do
local newval
if (val == true) then
--toggle the option
local b = callmethod(info, inputpos, tab, "get", opt)
if tab.tristate then
--cycle in true, nil, false order
if b then
b = nil
elseif b == nil then
b = false
else
b = true
end
else
b = not b
end
newval = b
else
--set the option as specified
if val==L["on"] then
newval = true
elseif val==L["off"] then
newval = false
elseif val==L["default"] then
newval = nil
end
end
do_final(info, inputpos, tab, "set", opt, newval)
end
elseif tab.type=="color" then
------------ color --------------------------------------------
local str = strtrim(strlower(str))
if str == "" then
--TODO: Show current value
return
end
local r, g, b, a
local hasAlpha = tab.hasAlpha
if type(hasAlpha) == "function" or type(hasAlpha) == "string" then
info.hasAlpha = hasAlpha
hasAlpha = callmethod(info, inputpos, tab, 'hasAlpha')
info.hasAlpha = nil
end
if hasAlpha then
if str:len() == 8 and str:find("^%x*$") then
--parse a hex string
r,g,b,a = tonumber(str:sub(1, 2), 16) / 255, tonumber(str:sub(3, 4), 16) / 255, tonumber(str:sub(5, 6), 16) / 255, tonumber(str:sub(7, 8), 16) / 255
else
--parse seperate values
r,g,b,a = str:match("^([%d%.]+) ([%d%.]+) ([%d%.]+) ([%d%.]+)$")
r,g,b,a = tonumber(r), tonumber(g), tonumber(b), tonumber(a)
end
if not (r and g and b and a) then
usererr(info, inputpos, format(L["'%s' - expected 'RRGGBBAA' or 'r g b a'."], str))
return
end
if r >= 0.0 and r <= 1.0 and g >= 0.0 and g <= 1.0 and b >= 0.0 and b <= 1.0 and a >= 0.0 and a <= 1.0 then
--values are valid
elseif r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 and a >= 0 and a <= 255 then
--values are valid 0..255, convert to 0..1
r = r / 255
g = g / 255
b = b / 255
a = a / 255
else
--values are invalid
usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0..1 or 0..255."], str))
end
else
a = 1.0
if str:len() == 6 and str:find("^%x*$") then
--parse a hex string
r,g,b = tonumber(str:sub(1, 2), 16) / 255, tonumber(str:sub(3, 4), 16) / 255, tonumber(str:sub(5, 6), 16) / 255
else
--parse seperate values
r,g,b = str:match("^([%d%.]+) ([%d%.]+) ([%d%.]+)$")
r,g,b = tonumber(r), tonumber(g), tonumber(b)
end
if not (r and g and b) then
usererr(info, inputpos, format(L["'%s' - expected 'RRGGBB' or 'r g b'."], str))
return
end
if r >= 0.0 and r <= 1.0 and g >= 0.0 and g <= 1.0 and b >= 0.0 and b <= 1.0 then
--values are valid
elseif r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 then
--values are valid 0..255, convert to 0..1
r = r / 255
g = g / 255
b = b / 255
else
--values are invalid
usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0-1 or 0-255."], str))
end
end
do_final(info, inputpos, tab, "set", r,g,b,a)
elseif tab.type=="keybinding" then
------------ keybinding --------------------------------------------
local str = strtrim(strlower(str))
if str == "" then
--TODO: Show current value
return
end
local value = keybindingValidateFunc(str:upper())
if value == false then
usererr(info, inputpos, format(L["'%s' - Invalid Keybinding."], str))
return
end
do_final(info, inputpos, tab, "set", value)
elseif tab.type=="description" then
------------ description --------------------
-- ignore description, GUI config only
else
err(info, inputpos, "unknown options table item type '"..tostring(tab.type).."'")
end
end
--- Handle the chat command.
-- This is usually called from a chat command handler to parse the command input as operations on an aceoptions table.\\
-- AceConfigCmd uses this function internally when a slash command is registered with `:CreateChatCommand`
-- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
-- @param appName The application name as given to `:RegisterOptionsTable()`
-- @param input The commandline input (as given by the WoW handler, i.e. without the command itself)
-- @usage
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceConsole-3.0")
-- -- Use AceConsole-3.0 to register a Chat Command
-- MyAddon:RegisterChatCommand("mychat", "ChatCommand")
--
-- -- Show the GUI if no input is supplied, otherwise handle the chat input.
-- function MyAddon:ChatCommand(input)
-- -- Assuming "MyOptions" is the appName of a valid options table
-- if not input or input:trim() == "" then
-- LibStub("AceConfigDialog-3.0"):Open("MyOptions")
-- else
-- LibStub("AceConfigCmd-3.0").HandleCommand(MyAddon, "mychat", "MyOptions", input)
-- end
-- end
function AceConfigCmd:HandleCommand(slashcmd, appName, input)
local optgetter = cfgreg:GetOptionsTable(appName)
if not optgetter then
error([[Usage: HandleCommand("slashcmd", "appName", "input"): 'appName' - no options table "]]..tostring(appName)..[[" has been registered]], 2)
end
local options = assert( optgetter("cmd", MAJOR) )
local info = { -- Don't try to recycle this, it gets handed off to callbacks and whatnot
[0] = slashcmd,
appName = appName,
options = options,
input = input,
self = self,
handler = self,
uiType = "cmd",
uiName = MAJOR,
}
handle(info, 1, options, 0) -- (info, inputpos, table, depth)
end
--- Utility function to create a slash command handler.
-- Also registers tab completion with AceTab
-- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
-- @param appName The application name as given to `:RegisterOptionsTable()`
function AceConfigCmd:CreateChatCommand(slashcmd, appName)
if not AceConsole then
AceConsole = LibStub(AceConsoleName)
end
if AceConsole.RegisterChatCommand(self, slashcmd, function(input)
AceConfigCmd.HandleCommand(self, slashcmd, appName, input) -- upgradable
end,
true) then -- succesfully registered so lets get the command -> app table in
commands[slashcmd] = appName
end
end
--- Utility function that returns the options table that belongs to a slashcommand.
-- Designed to be used for the AceTab interface.
-- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
-- @return The options table associated with the slash command (or nil if the slash command was not registered)
function AceConfigCmd:GetChatCommandOptions(slashcmd)
return commands[slashcmd]
end

4
TradeSkillMaster/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceConfigCmd-3.0.lua"/>
</Ui>

1962
TradeSkillMaster/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua

File diff suppressed because it is too large Load Diff

4
TradeSkillMaster/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceConfigDialog-3.0.lua"/>
</Ui>

371
TradeSkillMaster/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua

@ -0,0 +1,371 @@
--- AceConfigRegistry-3.0 handles central registration of options tables in use by addons and modules.\\
-- Options tables can be registered as raw tables, OR as function refs that return a table.\\
-- Such functions receive three arguments: "uiType", "uiName", "appName". \\
-- * Valid **uiTypes**: "cmd", "dropdown", "dialog". This is verified by the library at call time. \\
-- * The **uiName** field is expected to contain the full name of the calling addon, including version, e.g. "FooBar-1.0". This is verified by the library at call time.\\
-- * The **appName** field is the options table name as given at registration time \\
--
-- :IterateOptionsTables() (and :GetOptionsTable() if only given one argument) return a function reference that the requesting config handling addon must call with valid "uiType", "uiName".
-- @class file
-- @name AceConfigRegistry-3.0
-- @release $Id: AceConfigRegistry-3.0.lua 1207 2019-06-23 12:08:33Z nevcairiel $
local CallbackHandler = LibStub("CallbackHandler-1.0")
local MAJOR, MINOR = "AceConfigRegistry-3.0", 20
local AceConfigRegistry = LibStub:NewLibrary(MAJOR, MINOR)
if not AceConfigRegistry then return end
AceConfigRegistry.tables = AceConfigRegistry.tables or {}
if not AceConfigRegistry.callbacks then
AceConfigRegistry.callbacks = CallbackHandler:New(AceConfigRegistry)
end
-- Lua APIs
local tinsert, tconcat = table.insert, table.concat
local strfind, strmatch = string.find, string.match
local type, tostring, select, pairs = type, tostring, select, pairs
local error, assert = error, assert
-----------------------------------------------------------------------
-- Validating options table consistency:
AceConfigRegistry.validated = {
-- list of options table names ran through :ValidateOptionsTable automatically.
-- CLEARED ON PURPOSE, since newer versions may have newer validators
cmd = {},
dropdown = {},
dialog = {},
}
local function err(msg, errlvl, ...)
local t = {}
for i=select("#",...),1,-1 do
tinsert(t, (select(i, ...)))
end
error(MAJOR..":ValidateOptionsTable(): "..tconcat(t,".")..msg, errlvl+2)
end
local isstring={["string"]=true, _="string"}
local isstringfunc={["string"]=true,["function"]=true, _="string or funcref"}
local istable={["table"]=true, _="table"}
local ismethodtable={["table"]=true,["string"]=true,["function"]=true, _="methodname, funcref or table"}
local optstring={["nil"]=true,["string"]=true, _="string"}
local optstringfunc={["nil"]=true,["string"]=true,["function"]=true, _="string or funcref"}
local optstringnumberfunc={["nil"]=true,["string"]=true,["number"]=true,["function"]=true, _="string, number or funcref"}
local optnumber={["nil"]=true,["number"]=true, _="number"}
local optmethodfalse={["nil"]=true,["string"]=true,["function"]=true,["boolean"]={[false]=true}, _="methodname, funcref or false"}
local optmethodnumber={["nil"]=true,["string"]=true,["function"]=true,["number"]=true, _="methodname, funcref or number"}
local optmethodtable={["nil"]=true,["string"]=true,["function"]=true,["table"]=true, _="methodname, funcref or table"}
local optmethodbool={["nil"]=true,["string"]=true,["function"]=true,["boolean"]=true, _="methodname, funcref or boolean"}
local opttable={["nil"]=true,["table"]=true, _="table"}
local optbool={["nil"]=true,["boolean"]=true, _="boolean"}
local optboolnumber={["nil"]=true,["boolean"]=true,["number"]=true, _="boolean or number"}
local optstringnumber={["nil"]=true,["string"]=true,["number"]=true, _="string or number"}
local basekeys={
type=isstring,
name=isstringfunc,
desc=optstringfunc,
descStyle=optstring,
order=optmethodnumber,
validate=optmethodfalse,
confirm=optmethodbool,
confirmText=optstring,
disabled=optmethodbool,
hidden=optmethodbool,
guiHidden=optmethodbool,
dialogHidden=optmethodbool,
dropdownHidden=optmethodbool,
cmdHidden=optmethodbool,
icon=optstringnumberfunc,
iconCoords=optmethodtable,
handler=opttable,
get=optmethodfalse,
set=optmethodfalse,
func=optmethodfalse,
arg={["*"]=true},
width=optstringnumber,
}
local typedkeys={
header={
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
description={
image=optstringnumberfunc,
imageCoords=optmethodtable,
imageHeight=optnumber,
imageWidth=optnumber,
fontSize=optstringfunc,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
group={
args=istable,
plugins=opttable,
inline=optbool,
cmdInline=optbool,
guiInline=optbool,
dropdownInline=optbool,
dialogInline=optbool,
childGroups=optstring,
},
execute={
image=optstringnumberfunc,
imageCoords=optmethodtable,
imageHeight=optnumber,
imageWidth=optnumber,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
input={
pattern=optstring,
usage=optstring,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
multiline=optboolnumber,
},
toggle={
tristate=optbool,
image=optstringnumberfunc,
imageCoords=optmethodtable,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
tristate={
},
range={
min=optnumber,
softMin=optnumber,
max=optnumber,
softMax=optnumber,
step=optnumber,
bigStep=optnumber,
isPercent=optbool,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
select={
values=ismethodtable,
sorting=optmethodtable,
style={
["nil"]=true,
["string"]={dropdown=true,radio=true},
_="string: 'dropdown' or 'radio'"
},
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
itemControl=optstring,
},
multiselect={
values=ismethodtable,
style=optstring,
tristate=optbool,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
color={
hasAlpha=optmethodbool,
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
keybinding={
control=optstring,
dialogControl=optstring,
dropdownControl=optstring,
},
}
local function validateKey(k,errlvl,...)
errlvl=(errlvl or 0)+1
if type(k)~="string" then
err("["..tostring(k).."] - key is not a string", errlvl,...)
end
if strfind(k, "[%c\127]") then
err("["..tostring(k).."] - key name contained control characters", errlvl,...)
end
end
local function validateVal(v, oktypes, errlvl,...)
errlvl=(errlvl or 0)+1
local isok=oktypes[type(v)] or oktypes["*"]
if not isok then
err(": expected a "..oktypes._..", got '"..tostring(v).."'", errlvl,...)
end
if type(isok)=="table" then -- isok was a table containing specific values to be tested for!
if not isok[v] then
err(": did not expect "..type(v).." value '"..tostring(v).."'", errlvl,...)
end
end
end
local function validate(options,errlvl,...)
errlvl=(errlvl or 0)+1
-- basic consistency
if type(options)~="table" then
err(": expected a table, got a "..type(options), errlvl,...)
end
if type(options.type)~="string" then
err(".type: expected a string, got a "..type(options.type), errlvl,...)
end
-- get type and 'typedkeys' member
local tk = typedkeys[options.type]
if not tk then
err(".type: unknown type '"..options.type.."'", errlvl,...)
end
-- make sure that all options[] are known parameters
for k,v in pairs(options) do
if not (tk[k] or basekeys[k]) then
err(": unknown parameter", errlvl,tostring(k),...)
end
end
-- verify that required params are there, and that everything is the right type
for k,oktypes in pairs(basekeys) do
validateVal(options[k], oktypes, errlvl,k,...)
end
for k,oktypes in pairs(tk) do
validateVal(options[k], oktypes, errlvl,k,...)
end
-- extra logic for groups
if options.type=="group" then
for k,v in pairs(options.args) do
validateKey(k,errlvl,"args",...)
validate(v, errlvl,k,"args",...)
end
if options.plugins then
for plugname,plugin in pairs(options.plugins) do
if type(plugin)~="table" then
err(": expected a table, got '"..tostring(plugin).."'", errlvl,tostring(plugname),"plugins",...)
end
for k,v in pairs(plugin) do
validateKey(k,errlvl,tostring(plugname),"plugins",...)
validate(v, errlvl,k,tostring(plugname),"plugins",...)
end
end
end
end
end
--- Validates basic structure and integrity of an options table \\
-- Does NOT verify that get/set etc actually exist, since they can be defined at any depth
-- @param options The table to be validated
-- @param name The name of the table to be validated (shown in any error message)
-- @param errlvl (optional number) error level offset, default 0 (=errors point to the function calling :ValidateOptionsTable)
function AceConfigRegistry:ValidateOptionsTable(options,name,errlvl)
errlvl=(errlvl or 0)+1
name = name or "Optionstable"
if not options.name then
options.name=name -- bit of a hack, the root level doesn't really need a .name :-/
end
validate(options,errlvl,name)
end
--- Fires a "ConfigTableChange" callback for those listening in on it, allowing config GUIs to refresh.
-- You should call this function if your options table changed from any outside event, like a game event
-- or a timer.
-- @param appName The application name as given to `:RegisterOptionsTable()`
function AceConfigRegistry:NotifyChange(appName)
if not AceConfigRegistry.tables[appName] then return end
AceConfigRegistry.callbacks:Fire("ConfigTableChange", appName)
end
-- -------------------------------------------------------------------
-- Registering and retreiving options tables:
-- validateGetterArgs: helper function for :GetOptionsTable (or, rather, the getter functions returned by it)
local function validateGetterArgs(uiType, uiName, errlvl)
errlvl=(errlvl or 0)+2
if uiType~="cmd" and uiType~="dropdown" and uiType~="dialog" then
error(MAJOR..": Requesting options table: 'uiType' - invalid configuration UI type, expected 'cmd', 'dropdown' or 'dialog'", errlvl)
end
if not strmatch(uiName, "[A-Za-z]%-[0-9]") then -- Expecting e.g. "MyLib-1.2"
error(MAJOR..": Requesting options table: 'uiName' - badly formatted or missing version number. Expected e.g. 'MyLib-1.2'", errlvl)
end
end
--- Register an options table with the config registry.
-- @param appName The application name as given to `:RegisterOptionsTable()`
-- @param options The options table, OR a function reference that generates it on demand. \\
-- See the top of the page for info on arguments passed to such functions.
-- @param skipValidation Skip options table validation (primarily useful for extremely huge options, with a noticeable slowdown)
function AceConfigRegistry:RegisterOptionsTable(appName, options, skipValidation)
if type(options)=="table" then
if options.type~="group" then -- quick sanity checker
error(MAJOR..": RegisterOptionsTable(appName, options): 'options' - missing type='group' member in root group", 2)
end
AceConfigRegistry.tables[appName] = function(uiType, uiName, errlvl)
errlvl=(errlvl or 0)+1
validateGetterArgs(uiType, uiName, errlvl)
if not AceConfigRegistry.validated[uiType][appName] and not skipValidation then
AceConfigRegistry:ValidateOptionsTable(options, appName, errlvl) -- upgradable
AceConfigRegistry.validated[uiType][appName] = true
end
return options
end
elseif type(options)=="function" then
AceConfigRegistry.tables[appName] = function(uiType, uiName, errlvl)
errlvl=(errlvl or 0)+1
validateGetterArgs(uiType, uiName, errlvl)
local tab = assert(options(uiType, uiName, appName))
if not AceConfigRegistry.validated[uiType][appName] and not skipValidation then
AceConfigRegistry:ValidateOptionsTable(tab, appName, errlvl) -- upgradable
AceConfigRegistry.validated[uiType][appName] = true
end
return tab
end
else
error(MAJOR..": RegisterOptionsTable(appName, options): 'options' - expected table or function reference", 2)
end
end
--- Returns an iterator of ["appName"]=funcref pairs
function AceConfigRegistry:IterateOptionsTables()
return pairs(AceConfigRegistry.tables)
end
--- Query the registry for a specific options table.
-- If only appName is given, a function is returned which you
-- can call with (uiType,uiName) to get the table.\\
-- If uiType&uiName are given, the table is returned.
-- @param appName The application name as given to `:RegisterOptionsTable()`
-- @param uiType The type of UI to get the table for, one of "cmd", "dropdown", "dialog"
-- @param uiName The name of the library/addon querying for the table, e.g. "MyLib-1.0"
function AceConfigRegistry:GetOptionsTable(appName, uiType, uiName)
local f = AceConfigRegistry.tables[appName]
if not f then
return nil
end
if uiType then
return f(uiType,uiName,1) -- get the table for us
else
return f -- return the function
end
end

4
TradeSkillMaster/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceConfigRegistry-3.0.lua"/>
</Ui>

250
TradeSkillMaster/Libs/AceConsole-3.0/AceConsole-3.0.lua

@ -0,0 +1,250 @@
--- **AceConsole-3.0** provides registration facilities for slash commands.
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
-- to your addons individual needs.
--
-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceConsole itself.\\
-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceConsole.
-- @class file
-- @name AceConsole-3.0
-- @release $Id$
local MAJOR,MINOR = "AceConsole-3.0", 7
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceConsole then return end -- No upgrade needed
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
-- Lua APIs
local tconcat, tostring, select = table.concat, tostring, select
local type, pairs, error = type, pairs, error
local format, strfind, strsub = string.format, string.find, string.sub
local max = math.max
-- WoW APIs
local _G = _G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList
local tmp={}
local function Print(self,frame,...)
local n=0
if self ~= AceConsole then
n=n+1
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
end
for i=1, select("#", ...) do
n=n+1
tmp[n] = tostring(select(i, ...))
end
frame:AddMessage( tconcat(tmp," ",1,n) )
end
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
-- @paramsig [chatframe ,] ...
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
-- @param ... List of any values to be printed
function AceConsole:Print(...)
local frame = ...
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
return Print(self, frame, select(2,...))
else
return Print(self, DEFAULT_CHAT_FRAME, ...)
end
end
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
-- @paramsig [chatframe ,] "format"[, ...]
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
-- @param format Format string - same syntax as standard Lua format()
-- @param ... Arguments to the format string
function AceConsole:Printf(...)
local frame = ...
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
return Print(self, frame, format(select(2,...)))
else
return Print(self, DEFAULT_CHAT_FRAME, format(...))
end
end
--- Register a simple chat command
-- @param command Chat command to be registered WITHOUT leading "/"
-- @param func Function to call when the slash command is being used (funcref or methodname)
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
function AceConsole:RegisterChatCommand( command, func, persist )
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
local name = "ACECONSOLE_"..command:upper()
if type( func ) == "string" then
SlashCmdList[name] = function(input, editBox)
self[func](self, input, editBox)
end
else
SlashCmdList[name] = func
end
_G["SLASH_"..name.."1"] = "/"..command:lower()
AceConsole.commands[command] = name
-- non-persisting commands are registered for enabling disabling
if not persist then
if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end
AceConsole.weakcommands[self][command] = func
end
return true
end
--- Unregister a chatcommand
-- @param command Chat command to be unregistered WITHOUT leading "/"
function AceConsole:UnregisterChatCommand( command )
local name = AceConsole.commands[command]
if name then
SlashCmdList[name] = nil
_G["SLASH_" .. name .. "1"] = nil
hash_SlashCmdList["/" .. command:upper()] = nil
AceConsole.commands[command] = nil
end
end
--- Get an iterator over all Chat Commands registered with AceConsole
-- @return Iterator (pairs) over all commands
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
local function nils(n, ...)
if n>1 then
return nil, nils(n-1, ...)
elseif n==1 then
return nil, ...
else
return ...
end
end
--- Retreive one or more space-separated arguments from a string.
-- Treats quoted strings and itemlinks as non-spaced.
-- @param str The raw argument string
-- @param numargs How many arguments to get (default 1)
-- @param startpos Where in the string to start scanning (default 1)
-- @return Returns arg1, arg2, ..., nextposition\\
-- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string.
function AceConsole:GetArgs(str, numargs, startpos)
numargs = numargs or 1
startpos = max(startpos or 1, 1)
local pos=startpos
-- find start of new arg
pos = strfind(str, "[^ ]", pos)
if not pos then -- whoops, end of string
return nils(numargs, 1e9)
end
if numargs<1 then
return pos
end
-- quoted or space separated? find out which pattern to use
local delim_or_pipe
local ch = strsub(str, pos, pos)
if ch=='"' then
pos = pos + 1
delim_or_pipe='([|"])'
elseif ch=="'" then
pos = pos + 1
delim_or_pipe="([|'])"
else
delim_or_pipe="([| ])"
end
startpos = pos
while true do
-- find delimiter or hyperlink
local ch,_
pos,_,ch = strfind(str, delim_or_pipe, pos)
if not pos then break end
if ch=="|" then
-- some kind of escape
if strsub(str,pos,pos+1)=="|H" then
-- It's a |H....|hhyper link!|h
pos=strfind(str, "|h", pos+2) -- first |h
if not pos then break end
pos=strfind(str, "|h", pos+2) -- second |h
if not pos then break end
elseif strsub(str,pos, pos+1) == "|T" then
-- It's a |T....|t texture
pos=strfind(str, "|t", pos+2)
if not pos then break end
end
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
else
-- found delimiter, done with this arg
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
end
end
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
return strsub(str, startpos), nils(numargs-1, 1e9)
end
--- embedding and embed handling
local mixins = {
"Print",
"Printf",
"RegisterChatCommand",
"UnregisterChatCommand",
"GetArgs",
}
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceBucket in
function AceConsole:Embed( target )
for k, v in pairs( mixins ) do
target[v] = self[v]
end
self.embeds[target] = true
return target
end
function AceConsole:OnEmbedEnable( target )
if AceConsole.weakcommands[target] then
for command, func in pairs( AceConsole.weakcommands[target] ) do
target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry
end
end
end
function AceConsole:OnEmbedDisable( target )
if AceConsole.weakcommands[target] then
for command, func in pairs( AceConsole.weakcommands[target] ) do
target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care?
end
end
end
for addon in pairs(AceConsole.embeds) do
AceConsole:Embed(addon)
end

4
TradeSkillMaster/Libs/AceConsole-3.0/AceConsole-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceConsole-3.0.lua"/>
</Ui>

741
TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.lua

@ -0,0 +1,741 @@
--- **AceDB-3.0** manages the SavedVariables of your addon.
-- It offers profile management, smart defaults and namespaces for modules.\\
-- Data can be saved in different data-types, depending on its intended usage.
-- The most common data-type is the `profile` type, which allows the user to choose
-- the active profile, and manage the profiles of all of his characters.\\
-- The following data types are available:
-- * **char** Character-specific data. Every character has its own database.
-- * **realm** Realm-specific data. All of the players characters on the same realm share this database.
-- * **class** Class-specific data. All of the players characters of the same class share this database.
-- * **race** Race-specific data. All of the players characters of the same race share this database.
-- * **faction** Faction-specific data. All of the players characters of the same faction share this database.
-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database.
-- * **locale** Locale specific data, based on the locale of the players game client.
-- * **global** Global Data. All characters on the same account share this database.
-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used.
--
-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions
-- of the DBObjectLib listed here. \\
-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note
-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that,
-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases.
--
-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]].
--
-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs.
--
-- @usage
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample")
--
-- -- declare defaults to be used in the DB
-- local defaults = {
-- profile = {
-- setting = true,
-- }
-- }
--
-- function MyAddon:OnInitialize()
-- -- Assuming the .toc says ## SavedVariables: MyAddonDB
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true)
-- end
-- @class file
-- @name AceDB-3.0.lua
-- @release $Id$
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 27
local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
if not AceDB then return end -- No upgrade needed
-- Lua APIs
local type, pairs, next, error = type, pairs, next, error
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
-- WoW APIs
local _G = _G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub
AceDB.db_registry = AceDB.db_registry or {}
AceDB.frame = AceDB.frame or CreateFrame("Frame")
local CallbackHandler
local CallbackDummy = { Fire = function() end }
local DBObjectLib = {}
--[[-------------------------------------------------------------------------
AceDB Utility Functions
---------------------------------------------------------------------------]]
-- Simple shallow copy for copying defaults
local function copyTable(src, dest)
if type(dest) ~= "table" then dest = {} end
if type(src) == "table" then
for k,v in pairs(src) do
if type(v) == "table" then
-- try to index the key first so that the metatable creates the defaults, if set, and use that table
v = copyTable(v, dest[k])
end
dest[k] = v
end
end
return dest
end
-- Called to add defaults to a section of the database
--
-- When a ["*"] default section is indexed with a new key, a table is returned
-- and set in the host table. These tables must be cleaned up by removeDefaults
-- in order to ensure we don't write empty default tables.
local function copyDefaults(dest, src)
-- this happens if some value in the SV overwrites our default value with a non-table
--if type(dest) ~= "table" then return end
for k, v in pairs(src) do
if k == "*" or k == "**" then
if type(v) == "table" then
-- This is a metatable used for table defaults
local mt = {
-- This handles the lookup and creation of new subtables
__index = function(t,k)
if k == nil then return nil end
local tbl = {}
copyDefaults(tbl, v)
rawset(t, k, tbl)
return tbl
end,
}
setmetatable(dest, mt)
-- handle already existing tables in the SV
for dk, dv in pairs(dest) do
if not rawget(src, dk) and type(dv) == "table" then
copyDefaults(dv, v)
end
end
else
-- Values are not tables, so this is just a simple return
local mt = {__index = function(t,k) return k~=nil and v or nil end}
setmetatable(dest, mt)
end
elseif type(v) == "table" then
if not rawget(dest, k) then rawset(dest, k, {}) end
if type(dest[k]) == "table" then
copyDefaults(dest[k], v)
if src['**'] then
copyDefaults(dest[k], src['**'])
end
end
else
if rawget(dest, k) == nil then
rawset(dest, k, v)
end
end
end
end
-- Called to remove all defaults in the default table from the database
local function removeDefaults(db, defaults, blocker)
-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
setmetatable(db, nil)
-- loop through the defaults and remove their content
for k,v in pairs(defaults) do
if k == "*" or k == "**" then
if type(v) == "table" then
-- Loop through all the actual k,v pairs and remove
for key, value in pairs(db) do
if type(value) == "table" then
-- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables
if defaults[key] == nil and (not blocker or blocker[key] == nil) then
removeDefaults(value, v)
-- if the table is empty afterwards, remove it
if next(value) == nil then
db[key] = nil
end
-- if it was specified, only strip ** content, but block values which were set in the key table
elseif k == "**" then
removeDefaults(value, v, defaults[key])
end
end
end
elseif k == "*" then
-- check for non-table default
for key, value in pairs(db) do
if defaults[key] == nil and v == value then
db[key] = nil
end
end
end
elseif type(v) == "table" and type(db[k]) == "table" then
-- if a blocker was set, dive into it, to allow multi-level defaults
removeDefaults(db[k], v, blocker and blocker[k])
if next(db[k]) == nil then
db[k] = nil
end
else
-- check if the current value matches the default, and that its not blocked by another defaults table
if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then
db[k] = nil
end
end
end
end
-- This is called when a table section is first accessed, to set up the defaults
local function initSection(db, section, svstore, key, defaults)
local sv = rawget(db, "sv")
local tableCreated
if not sv[svstore] then sv[svstore] = {} end
if not sv[svstore][key] then
sv[svstore][key] = {}
tableCreated = true
end
local tbl = sv[svstore][key]
if defaults then
copyDefaults(tbl, defaults)
end
rawset(db, section, tbl)
return tableCreated, tbl
end
-- Metatable to handle the dynamic creation of sections and copying of sections.
local dbmt = {
__index = function(t, section)
local keys = rawget(t, "keys")
local key = keys[section]
if key then
local defaultTbl = rawget(t, "defaults")
local defaults = defaultTbl and defaultTbl[section]
if section == "profile" then
local new = initSection(t, section, "profiles", key, defaults)
if new then
-- Callback: OnNewProfile, database, newProfileKey
t.callbacks:Fire("OnNewProfile", t, key)
end
elseif section == "profiles" then
local sv = rawget(t, "sv")
if not sv.profiles then sv.profiles = {} end
rawset(t, "profiles", sv.profiles)
elseif section == "global" then
local sv = rawget(t, "sv")
if not sv.global then sv.global = {} end
if defaults then
copyDefaults(sv.global, defaults)
end
rawset(t, section, sv.global)
else
initSection(t, section, section, key, defaults)
end
end
return rawget(t, section)
end
}
local function validateDefaults(defaults, keyTbl, offset)
if not defaults then return end
offset = offset or 0
for k in pairs(defaults) do
if not keyTbl[k] or k == "profiles" then
error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset)
end
end
end
local preserve_keys = {
["callbacks"] = true,
["RegisterCallback"] = true,
["UnregisterCallback"] = true,
["UnregisterAllCallbacks"] = true,
["children"] = true,
}
local realmKey = GetRealmName()
local charKey = UnitName("player") .. " - " .. realmKey
local _, classKey = UnitClass("player")
local _, raceKey = UnitRace("player")
local factionKey = UnitFactionGroup("player")
local factionrealmKey = factionKey .. " - " .. realmKey
local factionrealmregionKey = factionrealmKey .. " - " .. string.sub(GetCVar("realmList"), 1, 2):upper()
local localeKey = GetLocale():lower()
-- Actual database initialization function
local function initdb(sv, defaults, defaultProfile, olddb, parent)
-- Generate the database keys for each section
-- map "true" to our "Default" profile
if defaultProfile == true then defaultProfile = "Default" end
local profileKey
if not parent then
-- Make a container for profile keys
if not sv.profileKeys then sv.profileKeys = {} end
-- Try to get the profile selected from the char db
profileKey = sv.profileKeys[charKey] or defaultProfile or charKey
-- save the selected profile for later
sv.profileKeys[charKey] = profileKey
else
-- Use the profile of the parents DB
profileKey = parent.keys.profile or defaultProfile or charKey
-- clear the profileKeys in the DB, namespaces don't need to store them
sv.profileKeys = nil
end
-- This table contains keys that enable the dynamic creation
-- of each section of the table. The 'global' and 'profiles'
-- have a key of true, since they are handled in a special case
local keyTbl= {
["char"] = charKey,
["realm"] = realmKey,
["class"] = classKey,
["race"] = raceKey,
["faction"] = factionKey,
["factionrealm"] = factionrealmKey,
["factionrealmregion"] = factionrealmregionKey,
["profile"] = profileKey,
["locale"] = localeKey,
["global"] = true,
["profiles"] = true,
}
validateDefaults(defaults, keyTbl, 1)
-- This allows us to use this function to reset an entire database
-- Clear out the old database
if olddb then
for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end
end
-- Give this database the metatable so it initializes dynamically
local db = setmetatable(olddb or {}, dbmt)
if not rawget(db, "callbacks") then
-- try to load CallbackHandler-1.0 if it loaded after our library
if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end
db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy
end
-- Copy methods locally into the database object, to avoid hitting
-- the metatable when calling methods
if not parent then
for name, func in pairs(DBObjectLib) do
db[name] = func
end
else
-- hack this one in
db.RegisterDefaults = DBObjectLib.RegisterDefaults
db.ResetProfile = DBObjectLib.ResetProfile
end
-- Set some properties in the database object
db.profiles = sv.profiles
db.keys = keyTbl
db.sv = sv
--db.sv_name = name
db.defaults = defaults
db.parent = parent
-- store the DB in the registry
AceDB.db_registry[db] = true
return db
end
-- handle PLAYER_LOGOUT
-- strip all defaults from all databases
-- and cleans up empty sections
local function logoutHandler(frame, event)
if event == "PLAYER_LOGOUT" then
for db in pairs(AceDB.db_registry) do
db.callbacks:Fire("OnDatabaseShutdown", db)
db:RegisterDefaults(nil)
-- cleanup sections that are empty without defaults
local sv = rawget(db, "sv")
for section in pairs(db.keys) do
if rawget(sv, section) then
-- global is special, all other sections have sub-entrys
-- also don't delete empty profiles on main dbs, only on namespaces
if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then
for key in pairs(sv[section]) do
if not next(sv[section][key]) then
sv[section][key] = nil
end
end
end
if not next(sv[section]) then
sv[section] = nil
end
end
end
end
end
end
AceDB.frame:RegisterEvent("PLAYER_LOGOUT")
AceDB.frame:SetScript("OnEvent", logoutHandler)
--[[-------------------------------------------------------------------------
AceDB Object Method Definitions
---------------------------------------------------------------------------]]
--- Sets the defaults table for the given database object by clearing any
-- that are currently set, and then setting the new defaults.
-- @param defaults A table of defaults for this database
function DBObjectLib:RegisterDefaults(defaults)
if defaults and type(defaults) ~= "table" then
error(("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2)
end
validateDefaults(defaults, self.keys)
-- Remove any currently set defaults
if self.defaults then
for section,key in pairs(self.keys) do
if self.defaults[section] and rawget(self, section) then
removeDefaults(self[section], self.defaults[section])
end
end
end
-- Set the DBObject.defaults table
self.defaults = defaults
-- Copy in any defaults, only touching those sections already created
if defaults then
for section,key in pairs(self.keys) do
if defaults[section] and rawget(self, section) then
copyDefaults(self[section], defaults[section])
end
end
end
end
--- Changes the profile of the database and all of it's namespaces to the
-- supplied named profile
-- @param name The name of the profile to set as the current profile
function DBObjectLib:SetProfile(name)
if type(name) ~= "string" then
error(("Usage: AceDBObject:SetProfile(name): 'name' - string expected, got %q."):format(type(name)), 2)
end
-- changing to the same profile, dont do anything
if name == self.keys.profile then return end
local oldProfile = self.profile
local defaults = self.defaults and self.defaults.profile
-- Callback: OnProfileShutdown, database
self.callbacks:Fire("OnProfileShutdown", self)
if oldProfile and defaults then
-- Remove the defaults from the old profile
removeDefaults(oldProfile, defaults)
end
self.profile = nil
self.keys["profile"] = name
-- if the storage exists, save the new profile
-- this won't exist on namespaces.
if self.sv.profileKeys then
self.sv.profileKeys[charKey] = name
end
-- populate to child namespaces
if self.children then
for _, db in pairs(self.children) do
DBObjectLib.SetProfile(db, name)
end
end
-- Callback: OnProfileChanged, database, newProfileKey
self.callbacks:Fire("OnProfileChanged", self, name)
end
--- Returns a table with the names of the existing profiles in the database.
-- You can optionally supply a table to re-use for this purpose.
-- @param tbl A table to store the profile names in (optional)
function DBObjectLib:GetProfiles(tbl)
if tbl and type(tbl) ~= "table" then
error(("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected, got %q."):format(type(tbl)), 2)
end
-- Clear the container table
if tbl then
for k,v in pairs(tbl) do tbl[k] = nil end
else
tbl = {}
end
local curProfile = self.keys.profile
local i = 0
for profileKey in pairs(self.profiles) do
i = i + 1
tbl[i] = profileKey
if curProfile and profileKey == curProfile then curProfile = nil end
end
-- Add the current profile, if it hasn't been created yet
if curProfile then
i = i + 1
tbl[i] = curProfile
end
return tbl, i
end
--- Returns the current profile name used by the database
function DBObjectLib:GetCurrentProfile()
return self.keys.profile
end
--- Deletes a named profile. This profile must not be the active profile.
-- @param name The name of the profile to be deleted
-- @param silent If true, do not raise an error when the profile does not exist
function DBObjectLib:DeleteProfile(name, silent)
if type(name) ~= "string" then
error(("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected, got %q."):format(type(name)), 2)
end
if self.keys.profile == name then
error(("Cannot delete the active profile (%q) in an AceDBObject."):format(name), 2)
end
if not rawget(self.profiles, name) and not silent then
error(("Cannot delete profile %q as it does not exist."):format(name), 2)
end
self.profiles[name] = nil
-- populate to child namespaces
if self.children then
for _, db in pairs(self.children) do
DBObjectLib.DeleteProfile(db, name, true)
end
end
-- switch all characters that use this profile back to the default
if self.sv.profileKeys then
for key, profile in pairs(self.sv.profileKeys) do
if profile == name then
self.sv.profileKeys[key] = nil
end
end
end
-- Callback: OnProfileDeleted, database, profileKey
self.callbacks:Fire("OnProfileDeleted", self, name)
end
--- Copies a named profile into the current profile, overwriting any conflicting
-- settings.
-- @param name The name of the profile to be copied into the current profile
-- @param silent If true, do not raise an error when the profile does not exist
function DBObjectLib:CopyProfile(name, silent)
if type(name) ~= "string" then
error(("Usage: AceDBObject:CopyProfile(name): 'name' - string expected, got %q."):format(type(name)), 2)
end
if name == self.keys.profile then
error(("Cannot have the same source and destination profiles (%q)."):format(name), 2)
end
if not rawget(self.profiles, name) and not silent then
error(("Cannot copy profile %q as it does not exist."):format(name), 2)
end
-- Reset the profile before copying
DBObjectLib.ResetProfile(self, nil, true)
local profile = self.profile
local source = self.profiles[name]
copyTable(source, profile)
-- populate to child namespaces
if self.children then
for _, db in pairs(self.children) do
DBObjectLib.CopyProfile(db, name, true)
end
end
-- Callback: OnProfileCopied, database, sourceProfileKey
self.callbacks:Fire("OnProfileCopied", self, name)
end
--- Resets the current profile to the default values (if specified).
-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object
-- @param noCallbacks if set to true, won't fire the OnProfileReset callback
function DBObjectLib:ResetProfile(noChildren, noCallbacks)
local profile = self.profile
for k,v in pairs(profile) do
profile[k] = nil
end
local defaults = self.defaults and self.defaults.profile
if defaults then
copyDefaults(profile, defaults)
end
-- populate to child namespaces
if self.children and not noChildren then
for _, db in pairs(self.children) do
DBObjectLib.ResetProfile(db, nil, noCallbacks)
end
end
-- Callback: OnProfileReset, database
if not noCallbacks then
self.callbacks:Fire("OnProfileReset", self)
end
end
--- Resets the entire database, using the string defaultProfile as the new default
-- profile.
-- @param defaultProfile The profile name to use as the default
function DBObjectLib:ResetDB(defaultProfile)
if defaultProfile and type(defaultProfile) ~= "string" then
error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected, got %q."):format(type(defaultProfile)), 2)
end
local sv = self.sv
for k,v in pairs(sv) do
sv[k] = nil
end
initdb(sv, self.defaults, defaultProfile, self)
-- fix the child namespaces
if self.children then
if not sv.namespaces then sv.namespaces = {} end
for name, db in pairs(self.children) do
if not sv.namespaces[name] then sv.namespaces[name] = {} end
initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self)
end
end
-- Callback: OnDatabaseReset, database
self.callbacks:Fire("OnDatabaseReset", self)
-- Callback: OnProfileChanged, database, profileKey
self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"])
return self
end
--- Creates a new database namespace, directly tied to the database. This
-- is a full scale database in it's own rights other than the fact that
-- it cannot control its profile individually
-- @param name The name of the new namespace
-- @param defaults A table of values to use as defaults
function DBObjectLib:RegisterNamespace(name, defaults)
if type(name) ~= "string" then
error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected, got %q."):format(type(name)), 2)
end
if defaults and type(defaults) ~= "table" then
error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2)
end
if self.children and self.children[name] then
error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace called %q already exists."):format(name), 2)
end
local sv = self.sv
if not sv.namespaces then sv.namespaces = {} end
if not sv.namespaces[name] then
sv.namespaces[name] = {}
end
local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self)
if not self.children then self.children = {} end
self.children[name] = newDB
return newDB
end
--- Returns an already existing namespace from the database object.
-- @param name The name of the new namespace
-- @param silent if true, the addon is optional, silently return nil if its not found
-- @usage
-- local namespace = self.db:GetNamespace('namespace')
-- @return the namespace object if found
function DBObjectLib:GetNamespace(name, silent)
if type(name) ~= "string" then
error(("Usage: AceDBObject:GetNamespace(name): 'name' - string expected, got %q."):format(type(name)), 2)
end
if not silent and not (self.children and self.children[name]) then
error(("Usage: AceDBObject:GetNamespace(name): 'name' - namespace %q does not exist."):format(name), 2)
end
if not self.children then self.children = {} end
return self.children[name]
end
--[[-------------------------------------------------------------------------
AceDB Exposed Methods
---------------------------------------------------------------------------]]
--- Creates a new database object that can be used to handle database settings and profiles.
-- By default, an empty DB is created, using a character specific profile.
--
-- You can override the default profile used by passing any profile name as the third argument,
-- or by passing //true// as the third argument to use a globally shared profile called "Default".
--
-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char"
-- will use a profile named "char", and not a character-specific profile.
-- @param tbl The name of variable, or table to use for the database
-- @param defaults A table of database defaults
-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default.
-- You can also pass //true// to use a shared global profile called "Default".
-- @usage
-- -- Create an empty DB using a character-specific default profile.
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB")
-- @usage
-- -- Create a DB using defaults and using a shared default profile
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true)
function AceDB:New(tbl, defaults, defaultProfile)
if type(tbl) == "string" then
local name = tbl
tbl = _G[name]
if not tbl then
tbl = {}
_G[name] = tbl
end
end
if type(tbl) ~= "table" then
error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected, got %q."):format(type(tbl)), 2)
end
if defaults and type(defaults) ~= "table" then
error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected, got %q."):format(type(defaults)), 2)
end
if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then
error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2)
end
return initdb(tbl, defaults, defaultProfile)
end
-- upgrade existing databases
for db in pairs(AceDB.db_registry) do
if not db.parent then
for name,func in pairs(DBObjectLib) do
db[name] = func
end
else
db.RegisterDefaults = DBObjectLib.RegisterDefaults
db.ResetProfile = DBObjectLib.ResetProfile
end
end

4
TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceDB-3.0.lua"/>
</Ui>

460
TradeSkillMaster/Libs/AceDBOptions-3.0/AceDBOptions-3.0.lua

@ -0,0 +1,460 @@
--- AceDBOptions-3.0 provides a universal AceConfig options screen for managing AceDB-3.0 profiles.
-- @class file
-- @name AceDBOptions-3.0
-- @release $Id$
local ACEDBO_MAJOR, ACEDBO_MINOR = "AceDBOptions-3.0", 15
local AceDBOptions = LibStub:NewLibrary(ACEDBO_MAJOR, ACEDBO_MINOR)
if not AceDBOptions then return end -- No upgrade needed
-- Lua APIs
local pairs, next = pairs, next
-- WoW APIs
local UnitClass = UnitClass
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: NORMAL_FONT_COLOR_CODE, FONT_COLOR_CODE_CLOSE
AceDBOptions.optionTables = AceDBOptions.optionTables or {}
AceDBOptions.handlers = AceDBOptions.handlers or {}
--[[
Localization of AceDBOptions-3.0
]]
local L = {
choose = "Existing Profiles",
choose_desc = "You can either create a new profile by entering a name in the editbox, or choose one of the already existing profiles.",
choose_sub = "Select one of your currently available profiles.",
copy = "Copy From",
copy_desc = "Copy the settings from one existing profile into the currently active profile.",
current = "Current Profile:",
default = "Default",
delete = "Delete a Profile",
delete_confirm = "Are you sure you want to delete the selected profile?",
delete_desc = "Delete existing and unused profiles from the database to save space, and cleanup the SavedVariables file.",
delete_sub = "Deletes a profile from the database.",
intro = "You can change the active database profile, so you can have different settings for every character.",
new = "New",
new_sub = "Create a new empty profile.",
profiles = "Profiles",
profiles_sub = "Manage Profiles",
reset = "Reset Profile",
reset_desc = "Reset the current profile back to its default values, in case your configuration is broken, or you simply want to start over.",
reset_sub = "Reset the current profile to the default",
}
local LOCALE = GetLocale()
if LOCALE == "deDE" then
L["choose"] = "Vorhandene Profile"
L["choose_desc"] = "Du kannst ein neues Profil erstellen, indem du einen neuen Namen in der Eingabebox 'Neu' eingibst, oder wähle eines der vorhandenen Profile aus."
L["choose_sub"] = "Wählt ein bereits vorhandenes Profil aus."
L["copy"] = "Kopieren von..."
L["copy_desc"] = "Kopiere die Einstellungen von einem vorhandenen Profil in das aktive Profil."
L["current"] = "Aktuelles Profil:"
L["default"] = "Standard"
L["delete"] = "Profil löschen"
L["delete_confirm"] = "Willst du das ausgewählte Profil wirklich löschen?"
L["delete_desc"] = "Lösche vorhandene oder unbenutzte Profile aus der Datenbank, um Platz zu sparen und die SavedVariables-Datei 'sauber' zu halten."
L["delete_sub"] = "Löscht ein Profil aus der Datenbank."
L["intro"] = "Hier kannst du das aktive Datenbankprofil ändern, damit du verschiedene Einstellungen für jeden Charakter erstellen kannst, wodurch eine sehr flexible Konfiguration möglich wird."
L["new"] = "Neu"
L["new_sub"] = "Ein neues Profil erstellen."
L["profiles"] = "Profile"
L["profiles_sub"] = "Profile verwalten"
L["reset"] = "Profil zurücksetzen"
L["reset_desc"] = "Setzt das momentane Profil auf Standardwerte zurück, für den Fall, dass mit der Konfiguration etwas schief lief oder weil du einfach neu starten willst."
L["reset_sub"] = "Das aktuelle Profil auf Standard zurücksetzen."
elseif LOCALE == "frFR" then
L["choose"] = "Profils existants"
L["choose_desc"] = "Vous pouvez créer un nouveau profil en entrant un nouveau nom dans la boîte de saisie, ou en choississant un des profils déjà existants."
L["choose_sub"] = "Permet de choisir un des profils déjà disponibles."
L["copy"] = "Copier à partir de"
L["copy_desc"] = "Copie les paramètres d'un profil déjà existant dans le profil actuellement actif."
L["current"] = "Profil actuel :"
L["default"] = "Défaut"
L["delete"] = "Supprimer un profil"
L["delete_confirm"] = "Etes-vous sûr de vouloir supprimer le profil sélectionné ?"
L["delete_desc"] = "Supprime les profils existants inutilisés de la base de données afin de gagner de la place et de nettoyer le fichier SavedVariables."
L["delete_sub"] = "Supprime un profil de la base de données."
L["intro"] = "Vous pouvez changer le profil actuel afin d'avoir des paramètres différents pour chaque personnage, permettant ainsi d'avoir une configuration très flexible."
L["new"] = "Nouveau"
L["new_sub"] = "Créée un nouveau profil vierge."
L["profiles"] = "Profils"
L["profiles_sub"] = "Gestion des profils"
L["reset"] = "Réinitialiser le profil"
L["reset_desc"] = "Réinitialise le profil actuel au cas où votre configuration est corrompue ou si vous voulez tout simplement faire table rase."
L["reset_sub"] = "Réinitialise le profil actuel avec les paramètres par défaut."
elseif LOCALE == "koKR" then
L["choose"] = "저장 중인 프로필"
L["choose_desc"] = "입력창에 새로운 이름을 입력하거나 저장 중인 프로필 중 하나를 선택하여 새로운 프로필을 만들 수 있습니다."
L["choose_sub"] = "현재 이용할 수 있는 프로필 중 하나를 선택합니다."
L["copy"] = "복사해오기"
L["copy_desc"] = "현재 사용 중인 프로필에 선택한 프로필의 설정을 복사합니다."
L["current"] = "현재 프로필:"
L["default"] = "기본값"
L["delete"] = "프로필 삭제"
L["delete_confirm"] = "정말로 선택한 프로필을 삭제할까요?"
L["delete_desc"] = "저장 공간 절약과 SavedVariables 파일의 정리를 위해 데이터베이스에서 사용하지 않는 프로필을 삭제하세요."
L["delete_sub"] = "데이터베이스의 프로필을 삭제합니다."
L["intro"] = "활성 데이터베이스 프로필을 변경할 수 있고, 각 캐릭터 별로 다른 설정을 할 수 있습니다."
L["new"] = "새로운 프로필"
L["new_sub"] = "새로운 프로필을 만듭니다."
L["profiles"] = "프로필"
L["profiles_sub"] = "프로필 관리"
L["reset"] = "프로필 초기화"
L["reset_desc"] = "설정이 깨졌거나 처음부터 다시 설정을 원하는 경우, 현재 프로필을 기본값으로 초기화하세요."
L["reset_sub"] = "현재 프로필을 기본값으로 초기화합니다"
elseif LOCALE == "esES" or LOCALE == "esMX" then
L["choose"] = "Perfiles existentes"
L["choose_desc"] = "Puedes crear un nuevo perfil introduciendo un nombre en el recuadro o puedes seleccionar un perfil de los ya existentes."
L["choose_sub"] = "Selecciona uno de los perfiles disponibles."
L["copy"] = "Copiar de"
L["copy_desc"] = "Copia los ajustes de un perfil existente al perfil actual."
L["current"] = "Perfil actual:"
L["default"] = "Por defecto"
L["delete"] = "Borrar un Perfil"
L["delete_confirm"] = "¿Estas seguro que quieres borrar el perfil seleccionado?"
L["delete_desc"] = "Borra los perfiles existentes y sin uso de la base de datos para ganar espacio y limpiar el archivo SavedVariables."
L["delete_sub"] = "Borra un perfil de la base de datos."
L["intro"] = "Puedes cambiar el perfil activo de tal manera que cada personaje tenga diferentes configuraciones."
L["new"] = "Nuevo"
L["new_sub"] = "Crear un nuevo perfil vacio."
L["profiles"] = "Perfiles"
L["profiles_sub"] = "Manejar Perfiles"
L["reset"] = "Reiniciar Perfil"
L["reset_desc"] = "Reinicia el perfil actual a los valores por defectos, en caso de que se haya estropeado la configuración o quieras volver a empezar de nuevo."
L["reset_sub"] = "Reinicar el perfil actual al de por defecto"
elseif LOCALE == "zhTW" then
L["choose"] = "現有的設定檔"
L["choose_desc"] = "您可以在文字方塊內輸入名字以建立新的設定檔,或是選擇一個現有的設定檔使用。"
L["choose_sub"] = "從當前可用的設定檔裡面選擇一個。"
L["copy"] = "複製自"
L["copy_desc"] = "從一個現有的設定檔,將設定複製到現在使用中的設定檔。"
L["current"] = "目前設定檔:"
L["default"] = "預設"
L["delete"] = "刪除一個設定檔"
L["delete_confirm"] = "確定要刪除所選擇的設定檔嗎?"
L["delete_desc"] = "從資料庫裡刪除不再使用的設定檔,以節省空間,並且清理 SavedVariables 檔案。"
L["delete_sub"] = "從資料庫裡刪除一個設定檔。"
L["intro"] = "您可以從資料庫中選擇一個設定檔來使用,如此就可以讓每個角色使用不同的設定。"
L["new"] = "新建"
L["new_sub"] = "新建一個空的設定檔。"
L["profiles"] = "設定檔"
L["profiles_sub"] = "管理設定檔"
L["reset"] = "重置設定檔"
L["reset_desc"] = "將現用的設定檔重置為預設值;用於設定檔損壞,或者單純想要重來的情況。"
L["reset_sub"] = "將目前的設定檔重置為預設值"
elseif LOCALE == "zhCN" then
L["choose"] = "现有的配置文件"
L["choose_desc"] = "你可以通过在文本框内输入一个名字创立一个新的配置文件,也可以选择一个已经存在的配置文件。"
L["choose_sub"] = "从当前可用的配置文件里面选择一个。"
L["copy"] = "复制自"
L["copy_desc"] = "从当前某个已保存的配置文件复制到当前正使用的配置文件。"
L["current"] = "当前配置文件:"
L["default"] = "默认"
L["delete"] = "删除一个配置文件"
L["delete_confirm"] = "你确定要删除所选择的配置文件么?"
L["delete_desc"] = "从数据库里删除不再使用的配置文件,以节省空间,并且清理SavedVariables文件。"
L["delete_sub"] = "从数据库里删除一个配置文件。"
L["intro"] = "你可以选择一个活动的数据配置文件,这样你的每个角色就可以拥有不同的设置值,可以给你的插件配置带来极大的灵活性。"
L["new"] = "新建"
L["new_sub"] = "新建一个空的配置文件。"
L["profiles"] = "配置文件"
L["profiles_sub"] = "管理配置文件"
L["reset"] = "重置配置文件"
L["reset_desc"] = "将当前的配置文件恢复到它的默认值,用于你的配置文件损坏,或者你只是想重来的情况。"
L["reset_sub"] = "将当前的配置文件恢复为默认值"
elseif LOCALE == "ruRU" then
L["choose"] = "Существующие профили"
L["choose_desc"] = "Вы можете создать новый профиль, введя название в поле ввода, или выбрать один из уже существующих профилей."
L["choose_sub"] = "Выбор одиного из уже доступных профилей"
L["copy"] = "Скопировать из"
L["copy_desc"] = "Скопировать настройки из выбранного профиля в активный."
L["current"] = "Текущий профиль:"
L["default"] = "По умолчанию"
L["delete"] = "Удалить профиль"
L["delete_confirm"] = "Вы уверены, что вы хотите удалить выбранный профиль?"
L["delete_desc"] = "Удалить существующий и неиспользуемый профиль из БД для сохранения места, и очистить SavedVariables файл."
L["delete_sub"] = "Удаление профиля из БД"
L["intro"] = "Изменяя активный профиль, вы можете задать различные настройки модификаций для каждого персонажа."
L["new"] = "Новый"
L["new_sub"] = "Создать новый чистый профиль"
L["profiles"] = "Профили"
L["profiles_sub"] = "Управление профилями"
L["reset"] = "Сброс профиля"
L["reset_desc"] = "Сбросить текущий профиль к стандартным настройкам, если ваша конфигурация испорчена или вы хотите настроить всё заново."
L["reset_sub"] = "Сброс текущего профиля на стандартный"
elseif LOCALE == "itIT" then
L["choose"] = "Profili Esistenti"
L["choose_desc"] = "Puoi creare un nuovo profilo digitando il nome della casella di testo, oppure scegliendone uno tra i profili già esistenti."
L["choose_sub"] = "Seleziona uno dei profili attualmente disponibili."
L["copy"] = "Copia Da"
L["copy_desc"] = "Copia le impostazioni da un profilo esistente, nel profilo attivo in questo momento."
L["current"] = "Profilo Attivo:"
L["default"] = "Standard"
L["delete"] = "Cancella un Profilo"
L["delete_confirm"] = "Sei sicuro di voler cancellare il profilo selezionato?"
L["delete_desc"] = "Cancella i profili non utilizzati dal database per risparmiare spazio e mantenere puliti i file di configurazione SavedVariables."
L["delete_sub"] = "Cancella un profilo dal Database."
L["intro"] = "Puoi cambiare il profilo attivo, in modo da usare impostazioni diverse per ogni personaggio."
L["new"] = "Nuovo"
L["new_sub"] = "Crea un nuovo profilo vuoto."
L["profiles"] = "Profili"
L["profiles_sub"] = "Gestisci Profili"
L["reset"] = "Reimposta Profilo"
L["reset_desc"] = "Riporta il tuo profilo attivo alle sue impostazioni predefinite, nel caso in cui la tua configurazione si sia corrotta, o semplicemente tu voglia re-inizializzarla."
L["reset_sub"] = "Reimposta il profilo ai suoi valori predefiniti."
elseif LOCALE == "ptBR" then
L["choose"] = "Perfis Existentes"
L["choose_desc"] = "Você pode tanto criar um perfil novo tanto digitando um nome na caixa de texto, quanto escolher um dos perfis já existentes."
L["choose_sub"] = "Selecione um de seus perfis atualmente disponíveis."
L["copy"] = "Copiar De"
L["copy_desc"] = "Copia as definições de um perfil existente no perfil atualmente ativo."
L["current"] = "Perfil Autal:"
L["default"] = "Padrão"
L["delete"] = "Remover um Perfil"
L["delete_confirm"] = "Tem certeza que deseja remover o perfil selecionado?"
L["delete_desc"] = "Remove perfis existentes e inutilizados do banco de dados para economizar espaço, e limpar o arquivo SavedVariables."
L["delete_sub"] = "Remove um perfil do banco de dados."
L["intro"] = "Você pode alterar o perfil do banco de dados ativo, para que possa ter definições diferentes para cada personagem."
L["new"] = "Novo"
L["new_sub"] = "Cria um novo perfil vazio."
L["profiles"] = "Perfis"
L["profiles_sub"] = "Gerenciar Perfis"
L["reset"] = "Resetar Perfil"
L["reset_desc"] = "Reseta o perfil atual para os valores padrões, no caso de sua configuração estar quebrada, ou simplesmente se deseja começar novamente."
L["reset_sub"] = "Resetar o perfil atual ao padrão"
end
local defaultProfiles
local tmpprofiles = {}
-- Get a list of available profiles for the specified database.
-- You can specify which profiles to include/exclude in the list using the two boolean parameters listed below.
-- @param db The db object to retrieve the profiles from
-- @param common If true, getProfileList will add the default profiles to the return list, even if they have not been created yet
-- @param nocurrent If true, then getProfileList will not display the current profile in the list
-- @return Hashtable of all profiles with the internal name as keys and the display name as value.
local function getProfileList(db, common, nocurrent)
local profiles = {}
-- copy existing profiles into the table
local currentProfile = db:GetCurrentProfile()
for i,v in pairs(db:GetProfiles(tmpprofiles)) do
if not (nocurrent and v == currentProfile) then
profiles[v] = v
end
end
-- add our default profiles to choose from ( or rename existing profiles)
for k,v in pairs(defaultProfiles) do
if (common or profiles[k]) and not (nocurrent and k == currentProfile) then
profiles[k] = v
end
end
return profiles
end
--[[
OptionsHandlerPrototype
prototype class for handling the options in a sane way
]]
local OptionsHandlerPrototype = {}
--[[ Reset the profile ]]
function OptionsHandlerPrototype:Reset()
self.db:ResetProfile()
end
--[[ Set the profile to value ]]
function OptionsHandlerPrototype:SetProfile(info, value)
self.db:SetProfile(value)
end
--[[ returns the currently active profile ]]
function OptionsHandlerPrototype:GetCurrentProfile()
return self.db:GetCurrentProfile()
end
--[[
List all active profiles
you can control the output with the .arg variable
currently four modes are supported
(empty) - return all available profiles
"nocurrent" - returns all available profiles except the currently active profile
"common" - returns all avaialble profiles + some commonly used profiles ("char - realm", "realm", "class", "Default")
"both" - common except the active profile
]]
function OptionsHandlerPrototype:ListProfiles(info)
local arg = info.arg
local profiles
if arg == "common" and not self.noDefaultProfiles then
profiles = getProfileList(self.db, true, nil)
elseif arg == "nocurrent" then
profiles = getProfileList(self.db, nil, true)
elseif arg == "both" then -- currently not used
profiles = getProfileList(self.db, (not self.noDefaultProfiles) and true, true)
else
profiles = getProfileList(self.db)
end
return profiles
end
function OptionsHandlerPrototype:HasNoProfiles(info)
local profiles = self:ListProfiles(info)
return ((not next(profiles)) and true or false)
end
--[[ Copy a profile ]]
function OptionsHandlerPrototype:CopyProfile(info, value)
self.db:CopyProfile(value)
end
--[[ Delete a profile from the db ]]
function OptionsHandlerPrototype:DeleteProfile(info, value)
self.db:DeleteProfile(value)
end
--[[ fill defaultProfiles with some generic values ]]
local function generateDefaultProfiles(db)
defaultProfiles = {
["Default"] = L["default"],
[db.keys.char] = db.keys.char,
[db.keys.realm] = db.keys.realm,
[db.keys.class] = UnitClass("player")
}
end
--[[ create and return a handler object for the db, or upgrade it if it already existed ]]
local function getOptionsHandler(db, noDefaultProfiles)
if not defaultProfiles then
generateDefaultProfiles(db)
end
local handler = AceDBOptions.handlers[db] or { db = db, noDefaultProfiles = noDefaultProfiles }
for k,v in pairs(OptionsHandlerPrototype) do
handler[k] = v
end
AceDBOptions.handlers[db] = handler
return handler
end
--[[
the real options table
]]
local optionsTable = {
desc = {
order = 1,
type = "description",
name = L["intro"] .. "\n",
},
descreset = {
order = 9,
type = "description",
name = L["reset_desc"],
},
reset = {
order = 10,
type = "execute",
name = L["reset"],
desc = L["reset_sub"],
func = "Reset",
},
current = {
order = 11,
type = "description",
name = function(info) return L["current"] .. " " .. NORMAL_FONT_COLOR_CODE .. info.handler:GetCurrentProfile() .. FONT_COLOR_CODE_CLOSE end,
width = "default",
},
choosedesc = {
order = 20,
type = "description",
name = "\n" .. L["choose_desc"],
},
new = {
name = L["new"],
desc = L["new_sub"],
type = "input",
order = 30,
get = false,
set = "SetProfile",
},
choose = {
name = L["choose"],
desc = L["choose_sub"],
type = "select",
order = 40,
get = "GetCurrentProfile",
set = "SetProfile",
values = "ListProfiles",
arg = "common",
},
copydesc = {
order = 50,
type = "description",
name = "\n" .. L["copy_desc"],
},
copyfrom = {
order = 60,
type = "select",
name = L["copy"],
desc = L["copy_desc"],
get = false,
set = "CopyProfile",
values = "ListProfiles",
disabled = "HasNoProfiles",
arg = "nocurrent",
},
deldesc = {
order = 70,
type = "description",
name = "\n" .. L["delete_desc"],
},
delete = {
order = 80,
type = "select",
name = L["delete"],
desc = L["delete_sub"],
get = false,
set = "DeleteProfile",
values = "ListProfiles",
disabled = "HasNoProfiles",
arg = "nocurrent",
confirm = true,
confirmText = L["delete_confirm"],
},
}
--- Get/Create a option table that you can use in your addon to control the profiles of AceDB-3.0.
-- @param db The database object to create the options table for.
-- @return The options table to be used in AceConfig-3.0
-- @usage
-- -- Assuming `options` is your top-level options table and `self.db` is your database:
-- options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
function AceDBOptions:GetOptionsTable(db, noDefaultProfiles)
local tbl = AceDBOptions.optionTables[db] or {
type = "group",
name = L["profiles"],
desc = L["profiles_sub"],
}
tbl.handler = getOptionsHandler(db, noDefaultProfiles)
tbl.args = optionsTable
AceDBOptions.optionTables[db] = tbl
return tbl
end
-- upgrade existing tables
for db,tbl in pairs(AceDBOptions.optionTables) do
tbl.handler = getOptionsHandler(db)
tbl.args = optionsTable
end

4
TradeSkillMaster/Libs/AceDBOptions-3.0/AceDBOptions-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceDBOptions-3.0.lua"/>
</Ui>

126
TradeSkillMaster/Libs/AceEvent-3.0/AceEvent-3.0.lua

@ -0,0 +1,126 @@
--- AceEvent-3.0 provides event registration and secure dispatching.
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
--
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceEvent.
-- @class file
-- @name AceEvent-3.0
-- @release $Id$
local CallbackHandler = LibStub("CallbackHandler-1.0")
local MAJOR, MINOR = "AceEvent-3.0", 4
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
if not AceEvent then return end
-- Lua APIs
local pairs = pairs
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
-- APIs and registry for blizzard events, using CallbackHandler lib
if not AceEvent.events then
AceEvent.events = CallbackHandler:New(AceEvent,
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
end
function AceEvent.events:OnUsed(target, eventname)
AceEvent.frame:RegisterEvent(eventname)
end
function AceEvent.events:OnUnused(target, eventname)
AceEvent.frame:UnregisterEvent(eventname)
end
-- APIs and registry for IPC messages, using CallbackHandler lib
if not AceEvent.messages then
AceEvent.messages = CallbackHandler:New(AceEvent,
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
)
AceEvent.SendMessage = AceEvent.messages.Fire
end
--- embedding and embed handling
local mixins = {
"RegisterEvent", "UnregisterEvent",
"RegisterMessage", "UnregisterMessage",
"SendMessage",
"UnregisterAllEvents", "UnregisterAllMessages",
}
--- Register for a Blizzard Event.
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
-- Any arguments to the event will be passed on after that.
-- @name AceEvent:RegisterEvent
-- @class function
-- @paramsig event[, callback [, arg]]
-- @param event The event to register for
-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name)
-- @param arg An optional argument to pass to the callback function
--- Unregister an event.
-- @name AceEvent:UnregisterEvent
-- @class function
-- @paramsig event
-- @param event The event to unregister
--- Register for a custom AceEvent-internal message.
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
-- Any arguments to the event will be passed on after that.
-- @name AceEvent:RegisterMessage
-- @class function
-- @paramsig message[, callback [, arg]]
-- @param message The message to register for
-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name)
-- @param arg An optional argument to pass to the callback function
--- Unregister a message
-- @name AceEvent:UnregisterMessage
-- @class function
-- @paramsig message
-- @param message The message to unregister
--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message.
-- @name AceEvent:SendMessage
-- @class function
-- @paramsig message, ...
-- @param message The message to send
-- @param ... Any arguments to the message
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceEvent in
function AceEvent:Embed(target)
for k, v in pairs(mixins) do
target[v] = self[v]
end
self.embeds[target] = true
return target
end
-- AceEvent:OnEmbedDisable( target )
-- target (object) - target object that is being disabled
--
-- Unregister all events messages etc when the target disables.
-- this method should be called by the target manually or by an addon framework
function AceEvent:OnEmbedDisable(target)
target:UnregisterAllEvents()
target:UnregisterAllMessages()
end
-- Script to fire blizzard events into the event listeners
local events = AceEvent.events
AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
events:Fire(event, ...)
end)
--- Finally: upgrade our old embeds
for target, v in pairs(AceEvent.embeds) do
AceEvent:Embed(target)
end

4
TradeSkillMaster/Libs/AceEvent-3.0/AceEvent-3.0.xml

@ -0,0 +1,4 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceEvent-3.0.lua"/>
</Ui>

1031
TradeSkillMaster/Libs/AceGUI-3.0/AceGUI-3.0.lua

File diff suppressed because it is too large Load Diff

29
TradeSkillMaster/Libs/AceGUI-3.0/AceGUI-3.0.xml

@ -0,0 +1,29 @@
<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/
..\FrameXML\UI.xsd">
<Script file="AceGUI-3.0.lua"/>
<!-- Container -->
<Script file="widgets\AceGUIContainer-BlizOptionsGroup.lua"/>
<Script file="widgets\AceGUIContainer-DropDownGroup.lua"/>
<Script file="widgets\AceGUIContainer-Frame.lua"/>
<Script file="widgets\AceGUIContainer-InlineGroup.lua"/>
<Script file="widgets\AceGUIContainer-ScrollFrame.lua"/>
<Script file="widgets\AceGUIContainer-SimpleGroup.lua"/>
<Script file="widgets\AceGUIContainer-TabGroup.lua"/>
<Script file="widgets\AceGUIContainer-TreeGroup.lua"/>
<Script file="widgets\AceGUIContainer-Window.lua"/>
<!-- Widgets -->
<Script file="widgets\AceGUIWidget-Button.lua"/>
<Script file="widgets\AceGUIWidget-Button-ElvUI.lua"/>
<Script file="widgets\AceGUIWidget-CheckBox.lua"/>
<Script file="widgets\AceGUIWidget-ColorPicker.lua"/>
<Script file="widgets\AceGUIWidget-DropDown.lua"/>
<Script file="widgets\AceGUIWidget-DropDown-Items.lua"/>
<Script file="widgets\AceGUIWidget-EditBox.lua"/>
<Script file="widgets\AceGUIWidget-Heading.lua"/>
<Script file="widgets\AceGUIWidget-Icon.lua"/>
<Script file="widgets\AceGUIWidget-InteractiveLabel.lua"/>
<Script file="widgets\AceGUIWidget-Keybinding.lua"/>
<Script file="widgets\AceGUIWidget-Label.lua"/>
<Script file="widgets\AceGUIWidget-MultiLineEditBox.lua"/>
<Script file="widgets\AceGUIWidget-Slider.lua"/>
</Ui>

138
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua

@ -0,0 +1,138 @@
--[[-----------------------------------------------------------------------------
BlizOptionsGroup Container
Simple container widget for the integration of AceGUI into the Blizzard Interface Options
-------------------------------------------------------------------------------]]
local Type, Version = "BlizOptionsGroup", 21
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local CreateFrame = CreateFrame
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function OnShow(frame)
frame.obj:Fire("OnShow")
end
local function OnHide(frame)
frame.obj:Fire("OnHide")
end
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function okay(frame)
frame.obj:Fire("okay")
end
local function cancel(frame)
frame.obj:Fire("cancel")
end
local function default(frame)
frame.obj:Fire("default")
end
local function refresh(frame)
frame.obj:Fire("refresh")
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetName()
self:SetTitle()
end,
-- ["OnRelease"] = nil,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 63
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 26
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["SetName"] = function(self, name, parent)
self.frame.name = name
self.frame.parent = parent
end,
["SetTitle"] = function(self, title)
local content = self.content
content:ClearAllPoints()
if not title or title == "" then
content:SetPoint("TOPLEFT", 10, -10)
self.label:SetText("")
else
content:SetPoint("TOPLEFT", 10, -40)
self.label:SetText(title)
end
content:SetPoint("BOTTOMRIGHT", -10, 10)
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame")
frame:Hide()
-- support functions for the Blizzard Interface Options
frame.okay = okay
frame.cancel = cancel
frame.default = default
frame.refresh = refresh
frame:SetScript("OnHide", OnHide)
frame:SetScript("OnShow", OnShow)
local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
label:SetPoint("TOPLEFT", 10, -15)
label:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 10, -45)
label:SetJustifyH("LEFT")
label:SetJustifyV("TOP")
--Container Support
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", 10, -10)
content:SetPoint("BOTTOMRIGHT", -10, 10)
local widget = {
label = label,
frame = frame,
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

157
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-DropDownGroup.lua

@ -0,0 +1,157 @@
--[[-----------------------------------------------------------------------------
DropdownGroup Container
Container controlled by a dropdown on the top.
-------------------------------------------------------------------------------]]
local Type, Version = "DropdownGroup", 21
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local assert, pairs, type = assert, pairs, type
-- WoW APIs
local CreateFrame = CreateFrame
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function SelectedGroup(self, event, value)
local group = self.parentgroup
local status = group.status or group.localstatus
status.selected = value
self.parentgroup:Fire("OnGroupSelected", value)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self.dropdown:SetText("")
self:SetDropdownWidth(200)
self:SetTitle("")
end,
["OnRelease"] = function(self)
self.dropdown.list = nil
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
end,
["SetTitle"] = function(self, title)
self.titletext:SetText(title)
self.dropdown.frame:ClearAllPoints()
if title and title ~= "" then
self.dropdown.frame:SetPoint("TOPRIGHT", -2, 0)
else
self.dropdown.frame:SetPoint("TOPLEFT", -1, 0)
end
end,
["SetGroupList"] = function(self,list,order)
self.dropdown:SetList(list,order)
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
end,
["SetGroup"] = function(self,group)
self.dropdown:SetValue(group)
local status = self.status or self.localstatus
status.selected = group
self:Fire("OnGroupSelected", group)
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 26
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 63
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["LayoutFinished"] = function(self, width, height)
self:SetHeight((height or 0) + 63)
end,
["SetDropdownWidth"] = function(self, width)
self.dropdown:SetWidth(width)
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local PaneBackdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
local function Constructor()
local frame = CreateFrame("Frame")
frame:SetHeight(100)
frame:SetWidth(100)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
local titletext = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
titletext:SetPoint("TOPLEFT", 4, -5)
titletext:SetPoint("TOPRIGHT", -4, -5)
titletext:SetJustifyH("LEFT")
titletext:SetHeight(18)
local dropdown = AceGUI:Create("Dropdown")
dropdown.frame:SetParent(frame)
dropdown.frame:SetFrameLevel(dropdown.frame:GetFrameLevel() + 2)
dropdown:SetCallback("OnValueChanged", SelectedGroup)
dropdown.frame:SetPoint("TOPLEFT", -1, 0)
dropdown.frame:Show()
dropdown:SetLabel("")
local border = CreateFrame("Frame", nil, frame)
border:SetPoint("TOPLEFT", 0, -26)
border:SetPoint("BOTTOMRIGHT", 0, 3)
border:SetBackdrop(PaneBackdrop)
border:SetBackdropColor(0.1,0.1,0.1,0.5)
border:SetBackdropBorderColor(0.4,0.4,0.4)
--Container Support
local content = CreateFrame("Frame", nil, border)
content:SetPoint("TOPLEFT", 10, -10)
content:SetPoint("BOTTOMRIGHT", -10, 10)
local widget = {
frame = frame,
localstatus = {},
titletext = titletext,
dropdown = dropdown,
border = border,
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
dropdown.parentgroup = widget
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

316
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua

@ -0,0 +1,316 @@
--[[-----------------------------------------------------------------------------
Frame Container
-------------------------------------------------------------------------------]]
local Type, Version = "Frame", 25
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs, assert, type = pairs, assert, type
local wipe = table.wipe
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: CLOSE
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Button_OnClick(frame)
PlaySound("gsTitleOptionExit")
frame.obj:Hide()
end
local function Frame_OnShow(frame)
frame.obj:Fire("OnShow")
end
local function Frame_OnClose(frame)
frame.obj:Fire("OnClose")
end
local function Frame_OnMouseDown(frame)
AceGUI:ClearFocus()
end
local function Title_OnMouseDown(frame)
frame:GetParent():StartMoving()
AceGUI:ClearFocus()
end
local function MoverSizer_OnMouseUp(mover)
local frame = mover:GetParent()
frame:StopMovingOrSizing()
local self = frame.obj
local status = self.status or self.localstatus
status.width = frame:GetWidth()
status.height = frame:GetHeight()
status.top = frame:GetTop()
status.left = frame:GetLeft()
end
local function SizerSE_OnMouseDown(frame)
frame:GetParent():StartSizing("BOTTOMRIGHT")
AceGUI:ClearFocus()
end
local function SizerS_OnMouseDown(frame)
frame:GetParent():StartSizing("BOTTOM")
AceGUI:ClearFocus()
end
local function SizerE_OnMouseDown(frame)
frame:GetParent():StartSizing("RIGHT")
AceGUI:ClearFocus()
end
local function StatusBar_OnEnter(frame)
frame.obj:Fire("OnEnterStatusBar")
end
local function StatusBar_OnLeave(frame)
frame.obj:Fire("OnLeaveStatusBar")
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self.frame:SetParent(UIParent)
self.frame:SetFrameStrata("FULLSCREEN_DIALOG")
self:SetTitle()
self:SetStatusText()
self:ApplyStatus()
self:Show()
self:EnableResize(true)
end,
["OnRelease"] = function(self)
self.status = nil
wipe(self.localstatus)
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 34
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 57
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["SetTitle"] = function(self, title)
self.titletext:SetText(title)
self.titlebg:SetWidth((self.titletext:GetWidth() or 0) + 10)
end,
["SetStatusText"] = function(self, text)
self.statustext:SetText(text)
end,
["Hide"] = function(self)
self.frame:Hide()
end,
["Show"] = function(self)
self.frame:Show()
end,
["EnableResize"] = function(self, state)
local func = state and "Show" or "Hide"
self.sizer_se[func](self.sizer_se)
self.sizer_s[func](self.sizer_s)
self.sizer_e[func](self.sizer_e)
end,
-- called to set an external table to store status in
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
self:ApplyStatus()
end,
["ApplyStatus"] = function(self)
local status = self.status or self.localstatus
local frame = self.frame
self:SetWidth(status.width or 700)
self:SetHeight(status.height or 500)
frame:ClearAllPoints()
if status.top and status.left then
frame:SetPoint("TOP", UIParent, "BOTTOM", 0, status.top)
frame:SetPoint("LEFT", UIParent, "LEFT", status.left, 0)
else
frame:SetPoint("CENTER")
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local FrameBackdrop = {
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true, tileSize = 32, edgeSize = 32,
insets = { left = 8, right = 8, top = 8, bottom = 8 }
}
local PaneBackdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
frame:Hide()
frame:EnableMouse(true)
frame:SetMovable(true)
frame:SetResizable(true)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
frame:SetBackdrop(FrameBackdrop)
frame:SetBackdropColor(0, 0, 0, 1)
frame:SetMinResize(400, 200)
frame:SetToplevel(true)
frame:SetScript("OnShow", Frame_OnShow)
frame:SetScript("OnHide", Frame_OnClose)
frame:SetScript("OnMouseDown", Frame_OnMouseDown)
local closebutton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
closebutton:SetScript("OnClick", Button_OnClick)
closebutton:SetPoint("BOTTOMRIGHT", -27, 17)
closebutton:SetHeight(20)
closebutton:SetWidth(100)
closebutton:SetText(CLOSE)
local statusbg = CreateFrame("Button", nil, frame)
statusbg:SetPoint("BOTTOMLEFT", 15, 15)
statusbg:SetPoint("BOTTOMRIGHT", -132, 15)
statusbg:SetHeight(24)
statusbg:SetBackdrop(PaneBackdrop)
statusbg:SetBackdropColor(0.1,0.1,0.1)
statusbg:SetBackdropBorderColor(0.4,0.4,0.4)
statusbg:SetScript("OnEnter", StatusBar_OnEnter)
statusbg:SetScript("OnLeave", StatusBar_OnLeave)
local statustext = statusbg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
statustext:SetPoint("TOPLEFT", 7, -2)
statustext:SetPoint("BOTTOMRIGHT", -7, 2)
statustext:SetHeight(20)
statustext:SetJustifyH("LEFT")
statustext:SetText("")
local titlebg = frame:CreateTexture(nil, "OVERLAY")
titlebg:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
titlebg:SetTexCoord(0.31, 0.67, 0, 0.63)
titlebg:SetPoint("TOP", 0, 12)
titlebg:SetWidth(100)
titlebg:SetHeight(40)
local title = CreateFrame("Frame", nil, frame)
title:EnableMouse(true)
title:SetScript("OnMouseDown", Title_OnMouseDown)
title:SetScript("OnMouseUp", MoverSizer_OnMouseUp)
title:SetAllPoints(titlebg)
local titletext = title:CreateFontString(nil, "OVERLAY", "GameFontNormal")
titletext:SetPoint("TOP", titlebg, "TOP", 0, -14)
local titlebg_l = frame:CreateTexture(nil, "OVERLAY")
titlebg_l:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
titlebg_l:SetTexCoord(0.21, 0.31, 0, 0.63)
titlebg_l:SetPoint("RIGHT", titlebg, "LEFT")
titlebg_l:SetWidth(30)
titlebg_l:SetHeight(40)
local titlebg_r = frame:CreateTexture(nil, "OVERLAY")
titlebg_r:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
titlebg_r:SetTexCoord(0.67, 0.77, 0, 0.63)
titlebg_r:SetPoint("LEFT", titlebg, "RIGHT")
titlebg_r:SetWidth(30)
titlebg_r:SetHeight(40)
local sizer_se = CreateFrame("Frame", nil, frame)
sizer_se:SetPoint("BOTTOMRIGHT")
sizer_se:SetWidth(25)
sizer_se:SetHeight(25)
sizer_se:EnableMouse()
sizer_se:SetScript("OnMouseDown",SizerSE_OnMouseDown)
sizer_se:SetScript("OnMouseUp", MoverSizer_OnMouseUp)
local line1 = sizer_se:CreateTexture(nil, "BACKGROUND")
line1:SetWidth(14)
line1:SetHeight(14)
line1:SetPoint("BOTTOMRIGHT", -8, 8)
line1:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
local x = 0.1 * 14/17
line1:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
local line2 = sizer_se:CreateTexture(nil, "BACKGROUND")
line2:SetWidth(8)
line2:SetHeight(8)
line2:SetPoint("BOTTOMRIGHT", -8, 8)
line2:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
local x = 0.1 * 8/17
line2:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
local sizer_s = CreateFrame("Frame", nil, frame)
sizer_s:SetPoint("BOTTOMRIGHT", -25, 0)
sizer_s:SetPoint("BOTTOMLEFT")
sizer_s:SetHeight(25)
sizer_s:EnableMouse(true)
sizer_s:SetScript("OnMouseDown", SizerS_OnMouseDown)
sizer_s:SetScript("OnMouseUp", MoverSizer_OnMouseUp)
local sizer_e = CreateFrame("Frame", nil, frame)
sizer_e:SetPoint("BOTTOMRIGHT", 0, 25)
sizer_e:SetPoint("TOPRIGHT")
sizer_e:SetWidth(25)
sizer_e:EnableMouse(true)
sizer_e:SetScript("OnMouseDown", SizerE_OnMouseDown)
sizer_e:SetScript("OnMouseUp", MoverSizer_OnMouseUp)
--Container Support
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", 17, -27)
content:SetPoint("BOTTOMRIGHT", -17, 40)
local widget = {
localstatus = {},
titletext = titletext,
statustext = statustext,
titlebg = titlebg,
sizer_se = sizer_se,
sizer_s = sizer_s,
sizer_e = sizer_e,
content = content,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
closebutton.obj, statusbg.obj = widget, widget
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

103
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua

@ -0,0 +1,103 @@
--[[-----------------------------------------------------------------------------
InlineGroup Container
Simple container widget that creates a visible "box" with an optional title.
-------------------------------------------------------------------------------]]
local Type, Version = "InlineGroup", 21
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetWidth(300)
self:SetHeight(100)
self:SetTitle("")
end,
-- ["OnRelease"] = nil,
["SetTitle"] = function(self,title)
self.titletext:SetText(title)
end,
["LayoutFinished"] = function(self, width, height)
if self.noAutoHeight then return end
self:SetHeight((height or 0) + 40)
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 20
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 20
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local PaneBackdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
local titletext = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
titletext:SetPoint("TOPLEFT", 14, 0)
titletext:SetPoint("TOPRIGHT", -14, 0)
titletext:SetJustifyH("LEFT")
titletext:SetHeight(18)
local border = CreateFrame("Frame", nil, frame)
border:SetPoint("TOPLEFT", 0, -17)
border:SetPoint("BOTTOMRIGHT", -1, 3)
border:SetBackdrop(PaneBackdrop)
border:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
border:SetBackdropBorderColor(0.4, 0.4, 0.4)
--Container Support
local content = CreateFrame("Frame", nil, border)
content:SetPoint("TOPLEFT", 10, -10)
content:SetPoint("BOTTOMRIGHT", -10, 10)
local widget = {
frame = frame,
content = content,
titletext = titletext,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

215
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua

@ -0,0 +1,215 @@
--[[-----------------------------------------------------------------------------
ScrollFrame Container
Plain container that scrolls its content and doesn't grow in height.
-------------------------------------------------------------------------------]]
local Type, Version = "ScrollFrame", 26
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs, assert, type = pairs, assert, type
local min, max, floor = math.min, math.max, math.floor
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function FixScrollOnUpdate(frame)
frame:SetScript("OnUpdate", nil)
frame.obj:FixScroll()
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function ScrollFrame_OnMouseWheel(frame, value)
frame.obj:MoveScroll(value)
end
local function ScrollFrame_OnSizeChanged(frame)
frame:SetScript("OnUpdate", FixScrollOnUpdate)
end
local function ScrollBar_OnScrollValueChanged(frame, value)
frame.obj:SetScroll(value)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetScroll(0)
self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
end,
["OnRelease"] = function(self)
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
self.scrollframe:SetPoint("BOTTOMRIGHT")
self.scrollbar:Hide()
self.scrollBarShown = nil
self.content.height, self.content.width, self.content.original_width = nil, nil, nil
end,
["SetScroll"] = function(self, value)
local status = self.status or self.localstatus
local viewheight = self.scrollframe:GetHeight()
local height = self.content:GetHeight()
local offset
if viewheight > height then
offset = 0
else
offset = floor((height - viewheight) / 1000.0 * value)
end
self.content:ClearAllPoints()
self.content:SetPoint("TOPLEFT", 0, offset)
self.content:SetPoint("TOPRIGHT", 0, offset)
status.offset = offset
status.scrollvalue = value
end,
["MoveScroll"] = function(self, value)
local status = self.status or self.localstatus
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
if self.scrollBarShown then
local diff = height - viewheight
local delta = 1
if value < 0 then
delta = -1
end
self.scrollbar:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000))
end
end,
["FixScroll"] = function(self)
if self.updateLock then return end
self.updateLock = true
local status = self.status or self.localstatus
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
local offset = status.offset or 0
-- Give us a margin of error of 2 pixels to stop some conditions that i would blame on floating point inaccuracys
-- No-one is going to miss 2 pixels at the bottom of the frame, anyhow!
if viewheight < height + 2 then
if self.scrollBarShown then
self.scrollBarShown = nil
self.scrollbar:Hide()
self.scrollbar:SetValue(0)
self.scrollframe:SetPoint("BOTTOMRIGHT")
if self.content.original_width then
self.content.width = self.content.original_width
end
self:DoLayout()
end
else
if not self.scrollBarShown then
self.scrollBarShown = true
self.scrollbar:Show()
self.scrollframe:SetPoint("BOTTOMRIGHT", -20, 0)
if self.content.original_width then
self.content.width = self.content.original_width - 20
end
self:DoLayout()
end
local value = (offset / (viewheight - height) * 1000)
if value > 1000 then value = 1000 end
self.scrollbar:SetValue(value)
self:SetScroll(value)
if value < 1000 then
self.content:ClearAllPoints()
self.content:SetPoint("TOPLEFT", 0, offset)
self.content:SetPoint("TOPRIGHT", 0, offset)
status.offset = offset
end
end
self.updateLock = nil
end,
["LayoutFinished"] = function(self, width, height)
self.content:SetHeight(height or 0 + 20)
-- update the scrollframe
self:FixScroll()
-- schedule another update when everything has "settled"
self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
if not status.scrollvalue then
status.scrollvalue = 0
end
end,
["OnWidthSet"] = function(self, width)
local content = self.content
content.width = width - (self.scrollBarShown and 20 or 0)
content.original_width = width
end,
["OnHeightSet"] = function(self, height)
local content = self.content
content.height = height
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
local num = AceGUI:GetNextWidgetNum(Type)
local scrollframe = CreateFrame("ScrollFrame", nil, frame)
scrollframe:SetPoint("TOPLEFT")
scrollframe:SetPoint("BOTTOMRIGHT")
scrollframe:EnableMouseWheel(true)
scrollframe:SetScript("OnMouseWheel", ScrollFrame_OnMouseWheel)
scrollframe:SetScript("OnSizeChanged", ScrollFrame_OnSizeChanged)
local scrollbar = CreateFrame("Slider", ("AceConfigDialogScrollFrame%dScrollBar"):format(num), scrollframe, "UIPanelScrollBarTemplate")
scrollbar:SetPoint("TOPLEFT", scrollframe, "TOPRIGHT", 4, -16)
scrollbar:SetPoint("BOTTOMLEFT", scrollframe, "BOTTOMRIGHT", 4, 16)
scrollbar:SetMinMaxValues(0, 1000)
scrollbar:SetValueStep(1)
scrollbar:SetValue(0)
scrollbar:SetWidth(16)
scrollbar:Hide()
-- set the script as the last step, so it doesn't fire yet
scrollbar:SetScript("OnValueChanged", ScrollBar_OnScrollValueChanged)
local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
scrollbg:SetAllPoints(scrollbar)
scrollbg:SetTexture(0, 0, 0, 0.4)
--Container Support
local content = CreateFrame("Frame", nil, scrollframe)
content:SetPoint("TOPLEFT")
content:SetPoint("TOPRIGHT")
content:SetHeight(400)
scrollframe:SetScrollChild(content)
local widget = {
localstatus = { scrollvalue = 0 },
scrollframe = scrollframe,
scrollbar = scrollbar,
content = content,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
scrollframe.obj, scrollbar.obj = widget, widget
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

69
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-SimpleGroup.lua

@ -0,0 +1,69 @@
--[[-----------------------------------------------------------------------------
SimpleGroup Container
Simple container widget that just groups widgets.
-------------------------------------------------------------------------------]]
local Type, Version = "SimpleGroup", 20
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetWidth(300)
self:SetHeight(100)
end,
-- ["OnRelease"] = nil,
["LayoutFinished"] = function(self, width, height)
if self.noAutoHeight then return end
self:SetHeight(height or 0)
end,
["OnWidthSet"] = function(self, width)
local content = self.content
content:SetWidth(width)
content.width = width
end,
["OnHeightSet"] = function(self, height)
local content = self.content
content:SetHeight(height)
content.height = height
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Frame", nil, UIParent)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
--Container Support
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT")
content:SetPoint("BOTTOMRIGHT")
local widget = {
frame = frame,
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

349
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua

@ -0,0 +1,349 @@
--[[-----------------------------------------------------------------------------
TabGroup Container
Container that uses tabs on top to switch between groups.
-------------------------------------------------------------------------------]]
local Type, Version = "TabGroup", 31
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs, ipairs, assert, type, wipe = pairs, ipairs, assert, type, wipe
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
local _G = _G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: PanelTemplates_TabResize, PanelTemplates_SetDisabledTabState, PanelTemplates_SelectTab, PanelTemplates_DeselectTab
-- local upvalue storage used by BuildTabs
local widths = {}
local rowwidths = {}
local rowends = {}
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function UpdateTabLook(frame)
if frame.disabled then
PanelTemplates_SetDisabledTabState(frame)
elseif frame.selected then
PanelTemplates_SelectTab(frame)
else
PanelTemplates_DeselectTab(frame)
end
end
local function Tab_SetText(frame, text)
frame:_SetText(text)
local width = frame.obj.frame.width or frame.obj.frame:GetWidth() or 0
PanelTemplates_TabResize(frame, 0, nil, width)
end
local function Tab_SetSelected(frame, selected)
frame.selected = selected
UpdateTabLook(frame)
end
local function Tab_SetDisabled(frame, disabled)
frame.disabled = disabled
UpdateTabLook(frame)
end
local function BuildTabsOnUpdate(frame)
local self = frame.obj
self:BuildTabs()
frame:SetScript("OnUpdate", nil)
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Tab_OnClick(frame)
if not (frame.selected or frame.disabled) then
PlaySound("igCharacterInfoTab")
frame.obj:SelectTab(frame.value)
end
end
local function Tab_OnEnter(frame)
local self = frame.obj
self:Fire("OnTabEnter", self.tabs[frame.id].value, frame)
end
local function Tab_OnLeave(frame)
local self = frame.obj
self:Fire("OnTabLeave", self.tabs[frame.id].value, frame)
end
local function Tab_OnShow(frame)
_G[frame:GetName().."HighlightTexture"]:SetWidth(frame:GetTextWidth() + 30)
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetTitle()
end,
["OnRelease"] = function(self)
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
self.tablist = nil
for _, tab in pairs(self.tabs) do
tab:Hide()
end
end,
["CreateTab"] = function(self, id)
local tabname = ("AceGUITabGroup%dTab%d"):format(self.num, id)
local tab = CreateFrame("Button", tabname, self.border, "OptionsFrameTabButtonTemplate")
tab.obj = self
tab.id = id
tab.text = _G[tabname .. "Text"]
tab.text:ClearAllPoints()
tab.text:SetPoint("LEFT", 14, -3)
tab.text:SetPoint("RIGHT", -12, -3)
tab:SetScript("OnClick", Tab_OnClick)
tab:SetScript("OnEnter", Tab_OnEnter)
tab:SetScript("OnLeave", Tab_OnLeave)
tab:SetScript("OnShow", Tab_OnShow)
tab._SetText = tab.SetText
tab.SetText = Tab_SetText
tab.SetSelected = Tab_SetSelected
tab.SetDisabled = Tab_SetDisabled
return tab
end,
["SetTitle"] = function(self, text)
self.titletext:SetText(text or "")
if text and text ~= "" then
self.alignoffset = 25
else
self.alignoffset = 18
end
self:BuildTabs()
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
end,
["SelectTab"] = function(self, value)
local status = self.status or self.localstatus
local found
for i, v in ipairs(self.tabs) do
if v.value == value then
v:SetSelected(true)
found = true
else
v:SetSelected(false)
end
end
status.selected = value
if found then
self:Fire("OnGroupSelected",value)
end
end,
["SetTabs"] = function(self, tabs)
self.tablist = tabs
self:BuildTabs()
end,
["BuildTabs"] = function(self)
local hastitle = (self.titletext:GetText() and self.titletext:GetText() ~= "")
local tablist = self.tablist
local tabs = self.tabs
if not tablist then return end
local width = self.frame.width or self.frame:GetWidth() or 0
wipe(widths)
wipe(rowwidths)
wipe(rowends)
--Place Text into tabs and get thier initial width
for i, v in ipairs(tablist) do
local tab = tabs[i]
if not tab then
tab = self:CreateTab(i)
tabs[i] = tab
end
tab:Show()
tab:SetText(v.text)
tab:SetDisabled(v.disabled)
tab.value = v.value
widths[i] = tab:GetWidth() - 6 --tabs are anchored 10 pixels from the right side of the previous one to reduce spacing, but add a fixed 4px padding for the text
end
for i = (#tablist)+1, #tabs, 1 do
tabs[i]:Hide()
end
--First pass, find the minimum number of rows needed to hold all tabs and the initial tab layout
local numtabs = #tablist
local numrows = 1
local usedwidth = 0
for i = 1, #tablist do
--If this is not the first tab of a row and there isn't room for it
if usedwidth ~= 0 and (width - usedwidth - widths[i]) < 0 then
rowwidths[numrows] = usedwidth + 10 --first tab in each row takes up an extra 10px
rowends[numrows] = i - 1
numrows = numrows + 1
usedwidth = 0
end
usedwidth = usedwidth + widths[i]
end
rowwidths[numrows] = usedwidth + 10 --first tab in each row takes up an extra 10px
rowends[numrows] = #tablist
--Fix for single tabs being left on the last row, move a tab from the row above if applicable
if numrows > 1 then
--if the last row has only one tab
if rowends[numrows-1] == numtabs-1 then
--if there are more than 2 tabs in the 2nd last row
if (numrows == 2 and rowends[numrows-1] > 2) or (rowends[numrows] - rowends[numrows-1] > 2) then
--move 1 tab from the second last row to the last, if there is enough space
if (rowwidths[numrows] + widths[numtabs-1]) <= width then
rowends[numrows-1] = rowends[numrows-1] - 1
rowwidths[numrows] = rowwidths[numrows] + widths[numtabs-1]
rowwidths[numrows-1] = rowwidths[numrows-1] - widths[numtabs-1]
end
end
end
end
--anchor the rows as defined and resize tabs to fill thier row
local starttab = 1
for row, endtab in ipairs(rowends) do
local first = true
for tabno = starttab, endtab do
local tab = tabs[tabno]
tab:ClearAllPoints()
if first then
tab:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 0, -(hastitle and 14 or 7)-(row-1)*20 )
first = false
else
tab:SetPoint("LEFT", tabs[tabno-1], "RIGHT", -10, 0)
end
end
-- equal padding for each tab to fill the available width,
-- if the used space is above 75% already
-- the 18 pixel is the typical width of a scrollbar, so we can have a tab group inside a scrolling frame,
-- and not have the tabs jump around funny when switching between tabs that need scrolling and those that don't
local padding = 0
if not (numrows == 1 and rowwidths[1] < width*0.75 - 18) then
padding = (width - rowwidths[row]) / (endtab - starttab+1)
end
for i = starttab, endtab do
PanelTemplates_TabResize(tabs[i], padding + 4, nil, width)
end
starttab = endtab + 1
end
self.borderoffset = (hastitle and 17 or 10)+((numrows)*20)
self.border:SetPoint("TOPLEFT", 1, -self.borderoffset)
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local contentwidth = width - 60
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
self:BuildTabs(self)
self.frame:SetScript("OnUpdate", BuildTabsOnUpdate)
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - (self.borderoffset + 23)
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["LayoutFinished"] = function(self, width, height)
if self.noAutoHeight then return end
self:SetHeight((height or 0) + (self.borderoffset + 23))
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local PaneBackdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame",nil,UIParent)
frame:SetHeight(100)
frame:SetWidth(100)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
local titletext = frame:CreateFontString(nil,"OVERLAY","GameFontNormal")
titletext:SetPoint("TOPLEFT", 14, 0)
titletext:SetPoint("TOPRIGHT", -14, 0)
titletext:SetJustifyH("LEFT")
titletext:SetHeight(18)
titletext:SetText("")
local border = CreateFrame("Frame", nil, frame)
border:SetPoint("TOPLEFT", 1, -27)
border:SetPoint("BOTTOMRIGHT", -1, 3)
border:SetBackdrop(PaneBackdrop)
border:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
border:SetBackdropBorderColor(0.4, 0.4, 0.4)
local content = CreateFrame("Frame", nil, border)
content:SetPoint("TOPLEFT", 10, -7)
content:SetPoint("BOTTOMRIGHT", -10, 7)
local widget = {
num = num,
frame = frame,
localstatus = {},
alignoffset = 18,
titletext = titletext,
border = border,
borderoffset = 27,
tabs = {},
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

705
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua

@ -0,0 +1,705 @@
--[[-----------------------------------------------------------------------------
TreeGroup Container
Container that uses a tree control to switch between groups.
-------------------------------------------------------------------------------]]
local Type, Version = "TreeGroup", 43
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type
local math_min, math_max, floor = math.min, math.max, floor
local select, tremove, unpack, tconcat = select, table.remove, unpack, table.concat
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: GameTooltip, FONT_COLOR_CODE_CLOSE
-- Recycling functions
local new, del
do
local pool = setmetatable({},{__mode='k'})
function new()
local t = next(pool)
if t then
pool[t] = nil
return t
else
return {}
end
end
function del(t)
for k in pairs(t) do
t[k] = nil
end
pool[t] = true
end
end
local DEFAULT_TREE_WIDTH = 175
local DEFAULT_TREE_SIZABLE = true
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function GetButtonUniqueValue(line)
local parent = line.parent
if parent and parent.value then
return GetButtonUniqueValue(parent).."\001"..line.value
else
return line.value
end
end
local function UpdateButton(button, treeline, selected, canExpand, isExpanded)
local self = button.obj
local toggle = button.toggle
local text = treeline.text or ""
local icon = treeline.icon
local iconCoords = treeline.iconCoords
local level = treeline.level
local value = treeline.value
local uniquevalue = treeline.uniquevalue
local disabled = treeline.disabled
button.treeline = treeline
button.value = value
button.uniquevalue = uniquevalue
if selected then
button:LockHighlight()
button.selected = true
else
button:UnlockHighlight()
button.selected = false
end
button.level = level
if ( level == 1 ) then
button:SetNormalFontObject("GameFontNormal")
button:SetHighlightFontObject("GameFontHighlight")
button.text:SetPoint("LEFT", (icon and 16 or 0) + 8, 2)
else
button:SetNormalFontObject("GameFontHighlightSmall")
button:SetHighlightFontObject("GameFontHighlightSmall")
button.text:SetPoint("LEFT", (icon and 16 or 0) + 8 * level, 2)
end
if disabled then
button:EnableMouse(false)
button.text:SetText("|cff808080"..text..FONT_COLOR_CODE_CLOSE)
else
button.text:SetText(text)
button:EnableMouse(true)
end
if icon then
button.icon:SetTexture(icon)
button.icon:SetPoint("LEFT", 8 * level, (level == 1) and 0 or 1)
else
button.icon:SetTexture(nil)
end
if iconCoords then
button.icon:SetTexCoord(unpack(iconCoords))
else
button.icon:SetTexCoord(0, 1, 0, 1)
end
if canExpand then
if not isExpanded then
toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP")
toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN")
else
toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP")
toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN")
end
toggle:Show()
else
toggle:Hide()
end
end
local function ShouldDisplayLevel(tree)
local result = false
for k, v in ipairs(tree) do
if v.children == nil and v.visible ~= false then
result = true
elseif v.children then
result = result or ShouldDisplayLevel(v.children)
end
if result then return result end
end
return false
end
local function addLine(self, v, tree, level, parent)
local line = new()
line.value = v.value
line.text = v.text
line.icon = v.icon
line.iconCoords = v.iconCoords
line.disabled = v.disabled
line.tree = tree
line.level = level
line.parent = parent
line.visible = v.visible
line.uniquevalue = GetButtonUniqueValue(line)
if v.children then
line.hasChildren = true
else
line.hasChildren = nil
end
self.lines[#self.lines+1] = line
return line
end
--fire an update after one frame to catch the treeframes height
local function FirstFrameUpdate(frame)
local self = frame.obj
frame:SetScript("OnUpdate", nil)
self:RefreshTree()
end
local function BuildUniqueValue(...)
local n = select('#', ...)
if n == 1 then
return ...
else
return (...).."\001"..BuildUniqueValue(select(2,...))
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Expand_OnClick(frame)
local button = frame.button
local self = button.obj
local status = (self.status or self.localstatus).groups
status[button.uniquevalue] = not status[button.uniquevalue]
self:RefreshTree()
end
local function Button_OnClick(frame)
local self = frame.obj
self:Fire("OnClick", frame.uniquevalue, frame.selected)
if not frame.selected then
self:SetSelected(frame.uniquevalue)
frame.selected = true
frame:LockHighlight()
self:RefreshTree()
end
AceGUI:ClearFocus()
end
local function Button_OnDoubleClick(button)
local self = button.obj
local status = (self.status or self.localstatus).groups
status[button.uniquevalue] = not status[button.uniquevalue]
self:RefreshTree()
end
local function Button_OnEnter(frame)
local self = frame.obj
self:Fire("OnButtonEnter", frame.uniquevalue, frame)
if self.enabletooltips then
GameTooltip:SetOwner(frame, "ANCHOR_NONE")
GameTooltip:SetPoint("LEFT",frame,"RIGHT")
GameTooltip:SetText(frame.text:GetText() or "", 1, .82, 0, 1)
GameTooltip:Show()
end
end
local function Button_OnLeave(frame)
local self = frame.obj
self:Fire("OnButtonLeave", frame.uniquevalue, frame)
if self.enabletooltips then
GameTooltip:Hide()
end
end
local function OnScrollValueChanged(frame, value)
if frame.obj.noupdate then return end
local self = frame.obj
local status = self.status or self.localstatus
status.scrollvalue = value
self:RefreshTree()
AceGUI:ClearFocus()
end
local function Tree_OnSizeChanged(frame)
frame.obj:RefreshTree()
end
local function Tree_OnMouseWheel(frame, delta)
local self = frame.obj
if self.showscroll then
local scrollbar = self.scrollbar
local min, max = scrollbar:GetMinMaxValues()
local value = scrollbar:GetValue()
local newvalue = math_min(max,math_max(min,value - delta))
if value ~= newvalue then
scrollbar:SetValue(newvalue)
end
end
end
local function Dragger_OnLeave(frame)
frame:SetBackdropColor(1, 1, 1, 0)
end
local function Dragger_OnEnter(frame)
frame:SetBackdropColor(1, 1, 1, 0.8)
end
local function Dragger_OnMouseDown(frame)
local treeframe = frame:GetParent()
treeframe:StartSizing("RIGHT")
end
local function Dragger_OnMouseUp(frame)
local treeframe = frame:GetParent()
local self = treeframe.obj
local treeframeParent = treeframe:GetParent()
treeframe:StopMovingOrSizing()
--treeframe:SetScript("OnUpdate", nil)
treeframe:SetUserPlaced(false)
--Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize
treeframe:SetHeight(0)
treeframe:ClearAllPoints()
treeframe:SetPoint("TOPLEFT", treeframeParent, "TOPLEFT",0,0)
treeframe:SetPoint("BOTTOMLEFT", treeframeParent, "BOTTOMLEFT",0,0)
local status = self.status or self.localstatus
status.treewidth = treeframe:GetWidth()
treeframe.obj:Fire("OnTreeResize",treeframe:GetWidth())
-- recalculate the content width
treeframe.obj:OnWidthSet(status.fullwidth)
-- update the layout of the content
treeframe.obj:DoLayout()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetTreeWidth(DEFAULT_TREE_WIDTH, DEFAULT_TREE_SIZABLE)
self:EnableButtonTooltips(true)
end,
["OnRelease"] = function(self)
self.status = nil
for k, v in pairs(self.localstatus) do
if k == "groups" then
for k2 in pairs(v) do
v[k2] = nil
end
else
self.localstatus[k] = nil
end
end
self.localstatus.scrollvalue = 0
self.localstatus.treewidth = DEFAULT_TREE_WIDTH
self.localstatus.treesizable = DEFAULT_TREE_SIZABLE
end,
["EnableButtonTooltips"] = function(self, enable)
self.enabletooltips = enable
end,
["CreateButton"] = function(self)
local num = AceGUI:GetNextWidgetNum("TreeGroupButton")
local button = CreateFrame("Button", ("AceGUI30TreeButton%d"):format(num), self.treeframe, "OptionsListButtonTemplate")
button.obj = self
local icon = button:CreateTexture(nil, "OVERLAY")
icon:SetWidth(14)
icon:SetHeight(14)
button.icon = icon
button:SetScript("OnClick",Button_OnClick)
button:SetScript("OnDoubleClick", Button_OnDoubleClick)
button:SetScript("OnEnter",Button_OnEnter)
button:SetScript("OnLeave",Button_OnLeave)
button.toggle.button = button
button.toggle:SetScript("OnClick",Expand_OnClick)
button.text:SetHeight(14) -- Prevents text wrapping
return button
end,
["SetStatusTable"] = function(self, status)
assert(type(status) == "table")
self.status = status
if not status.groups then
status.groups = {}
end
if not status.scrollvalue then
status.scrollvalue = 0
end
if not status.treewidth then
status.treewidth = DEFAULT_TREE_WIDTH
end
if status.treesizable == nil then
status.treesizable = DEFAULT_TREE_SIZABLE
end
self:SetTreeWidth(status.treewidth,status.treesizable)
self:RefreshTree()
end,
--sets the tree to be displayed
["SetTree"] = function(self, tree, filter)
self.filter = filter
if tree then
assert(type(tree) == "table")
end
self.tree = tree
self:RefreshTree()
end,
["BuildLevel"] = function(self, tree, level, parent)
local groups = (self.status or self.localstatus).groups
for i, v in ipairs(tree) do
if v.children then
if not self.filter or ShouldDisplayLevel(v.children) then
local line = addLine(self, v, tree, level, parent)
if groups[line.uniquevalue] then
self:BuildLevel(v.children, level+1, line)
end
end
elseif v.visible ~= false or not self.filter then
addLine(self, v, tree, level, parent)
end
end
end,
["RefreshTree"] = function(self,scrollToSelection)
local buttons = self.buttons
local lines = self.lines
for i, v in ipairs(buttons) do
v:Hide()
end
while lines[1] do
local t = tremove(lines)
for k in pairs(t) do
t[k] = nil
end
del(t)
end
if not self.tree then return end
--Build the list of visible entries from the tree and status tables
local status = self.status or self.localstatus
local groupstatus = status.groups
local tree = self.tree
local treeframe = self.treeframe
status.scrollToSelection = status.scrollToSelection or scrollToSelection -- needs to be cached in case the control hasn't been drawn yet (code bails out below)
self:BuildLevel(tree, 1)
local numlines = #lines
local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18))
if maxlines <= 0 then return end
local first, last
scrollToSelection = status.scrollToSelection
status.scrollToSelection = nil
if numlines <= maxlines then
--the whole tree fits in the frame
status.scrollvalue = 0
self:ShowScroll(false)
first, last = 1, numlines
else
self:ShowScroll(true)
--scrolling will be needed
self.noupdate = true
self.scrollbar:SetMinMaxValues(0, numlines - maxlines)
--check if we are scrolled down too far
if numlines - status.scrollvalue < maxlines then
status.scrollvalue = numlines - maxlines
end
self.noupdate = nil
first, last = status.scrollvalue+1, status.scrollvalue + maxlines
--show selection?
if scrollToSelection and status.selected then
local show
for i,line in ipairs(lines) do -- find the line number
if line.uniquevalue==status.selected then
show=i
end
end
if not show then
-- selection was deleted or something?
elseif show>=first and show<=last then
-- all good
else
-- scrolling needed!
if show<first then
status.scrollvalue = show-1
else
status.scrollvalue = show-maxlines
end
first, last = status.scrollvalue+1, status.scrollvalue + maxlines
end
end
if self.scrollbar:GetValue() ~= status.scrollvalue then
self.scrollbar:SetValue(status.scrollvalue)
end
end
local buttonnum = 1
for i = first, last do
local line = lines[i]
local button = buttons[buttonnum]
if not button then
button = self:CreateButton()
buttons[buttonnum] = button
button:SetParent(treeframe)
button:SetFrameLevel(treeframe:GetFrameLevel()+1)
button:ClearAllPoints()
if buttonnum == 1 then
if self.showscroll then
button:SetPoint("TOPRIGHT", -22, -10)
button:SetPoint("TOPLEFT", 0, -10)
else
button:SetPoint("TOPRIGHT", 0, -10)
button:SetPoint("TOPLEFT", 0, -10)
end
else
button:SetPoint("TOPRIGHT", buttons[buttonnum-1], "BOTTOMRIGHT",0,0)
button:SetPoint("TOPLEFT", buttons[buttonnum-1], "BOTTOMLEFT",0,0)
end
end
UpdateButton(button, line, status.selected == line.uniquevalue, line.hasChildren, groupstatus[line.uniquevalue] )
button:Show()
buttonnum = buttonnum + 1
end
end,
["SetSelected"] = function(self, value)
local status = self.status or self.localstatus
if status.selected ~= value then
status.selected = value
self:Fire("OnGroupSelected", value)
end
end,
["Select"] = function(self, uniquevalue, ...)
self.filter = false
local status = self.status or self.localstatus
local groups = status.groups
local path = {...}
for i = 1, #path do
groups[tconcat(path, "\001", 1, i)] = true
end
status.selected = uniquevalue
self:RefreshTree(true)
self:Fire("OnGroupSelected", uniquevalue)
end,
["SelectByPath"] = function(self, ...)
self:Select(BuildUniqueValue(...), ...)
end,
["SelectByValue"] = function(self, uniquevalue)
self:Select(uniquevalue, ("\001"):split(uniquevalue))
end,
["ShowScroll"] = function(self, show)
self.showscroll = show
if show then
self.scrollbar:Show()
if self.buttons[1] then
self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
end
else
self.scrollbar:Hide()
if self.buttons[1] then
self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
end
end
end,
["OnWidthSet"] = function(self, width)
local content = self.content
local treeframe = self.treeframe
local status = self.status or self.localstatus
status.fullwidth = width
local contentwidth = width - status.treewidth - 20
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
local maxtreewidth = math_min(400, width - 50)
if maxtreewidth > 100 and status.treewidth > maxtreewidth then
self:SetTreeWidth(maxtreewidth, status.treesizable)
end
treeframe:SetMaxResize(maxtreewidth, 1600)
end,
["OnHeightSet"] = function(self, height)
local content = self.content
local contentheight = height - 20
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end,
["SetTreeWidth"] = function(self, treewidth, resizable)
if not resizable then
if type(treewidth) == 'number' then
resizable = false
elseif type(treewidth) == 'boolean' then
resizable = treewidth
treewidth = DEFAULT_TREE_WIDTH
else
resizable = false
treewidth = DEFAULT_TREE_WIDTH
end
end
self.treeframe:SetWidth(treewidth)
self.dragger:EnableMouse(resizable)
local status = self.status or self.localstatus
status.treewidth = treewidth
status.treesizable = resizable
-- recalculate the content width
if status.fullwidth then
self:OnWidthSet(status.fullwidth)
end
end,
["GetTreeWidth"] = function(self)
local status = self.status or self.localstatus
return status.treewidth or DEFAULT_TREE_WIDTH
end,
["LayoutFinished"] = function(self, width, height)
if self.noAutoHeight then return end
self:SetHeight((height or 0) + 20)
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local PaneBackdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 3, right = 3, top = 5, bottom = 3 }
}
local DraggerBackdrop = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = nil,
tile = true, tileSize = 16, edgeSize = 0,
insets = { left = 3, right = 3, top = 7, bottom = 7 }
}
local function Constructor()
local num = AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Frame", nil, UIParent)
local treeframe = CreateFrame("Frame", nil, frame)
treeframe:SetPoint("TOPLEFT")
treeframe:SetPoint("BOTTOMLEFT")
treeframe:SetWidth(DEFAULT_TREE_WIDTH)
treeframe:EnableMouseWheel(true)
treeframe:SetBackdrop(PaneBackdrop)
treeframe:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
treeframe:SetBackdropBorderColor(0.4, 0.4, 0.4)
treeframe:SetResizable(true)
treeframe:SetMinResize(100, 1)
treeframe:SetMaxResize(400, 1600)
treeframe:SetScript("OnUpdate", FirstFrameUpdate)
treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged)
treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel)
local dragger = CreateFrame("Frame", nil, treeframe)
dragger:SetWidth(8)
dragger:SetPoint("TOP", treeframe, "TOPRIGHT")
dragger:SetPoint("BOTTOM", treeframe, "BOTTOMRIGHT")
dragger:SetBackdrop(DraggerBackdrop)
dragger:SetBackdropColor(1, 1, 1, 0)
dragger:SetScript("OnEnter", Dragger_OnEnter)
dragger:SetScript("OnLeave", Dragger_OnLeave)
dragger:SetScript("OnMouseDown", Dragger_OnMouseDown)
dragger:SetScript("OnMouseUp", Dragger_OnMouseUp)
local scrollbar = CreateFrame("Slider", ("AceConfigDialogTreeGroup%dScrollBar"):format(num), treeframe, "UIPanelScrollBarTemplate")
scrollbar:SetScript("OnValueChanged", nil)
scrollbar:SetPoint("TOPRIGHT", -10, -26)
scrollbar:SetPoint("BOTTOMRIGHT", -10, 26)
scrollbar:SetMinMaxValues(0,0)
scrollbar:SetValueStep(1)
scrollbar:SetValue(0)
scrollbar:SetWidth(16)
scrollbar:SetScript("OnValueChanged", OnScrollValueChanged)
local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
scrollbg:SetAllPoints(scrollbar)
scrollbg:SetTexture(0,0,0,0.4)
local border = CreateFrame("Frame",nil,frame)
border:SetPoint("TOPLEFT", treeframe, "TOPRIGHT")
border:SetPoint("BOTTOMRIGHT")
border:SetBackdrop(PaneBackdrop)
border:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
border:SetBackdropBorderColor(0.4, 0.4, 0.4)
--Container Support
local content = CreateFrame("Frame", nil, border)
content:SetPoint("TOPLEFT", 10, -10)
content:SetPoint("BOTTOMRIGHT", -10, 10)
local widget = {
frame = frame,
lines = {},
levels = {},
buttons = {},
hasChildren = {},
localstatus = { groups = {}, scrollvalue = 0 },
filter = false,
treeframe = treeframe,
dragger = dragger,
scrollbar = scrollbar,
border = border,
content = content,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
treeframe.obj, dragger.obj, scrollbar.obj = widget, widget, widget
return AceGUI:RegisterAsContainer(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

336
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua

@ -0,0 +1,336 @@
local AceGUI = LibStub("AceGUI-3.0")
-- Lua APIs
local pairs, assert, type = pairs, assert, type
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: GameFontNormal
----------------
-- Main Frame --
----------------
--[[
Events :
OnClose
]]
do
local Type = "Window"
local Version = 5
local function frameOnShow(this)
this.obj:Fire("OnShow")
end
local function frameOnClose(this)
this.obj:Fire("OnClose")
end
local function closeOnClick(this)
PlaySound("gsTitleOptionExit")
this.obj:Hide()
end
local function frameOnMouseDown(this)
AceGUI:ClearFocus()
end
local function titleOnMouseDown(this)
this:GetParent():StartMoving()
AceGUI:ClearFocus()
end
local function frameOnMouseUp(this)
local frame = this:GetParent()
frame:StopMovingOrSizing()
local self = frame.obj
local status = self.status or self.localstatus
status.width = frame:GetWidth()
status.height = frame:GetHeight()
status.top = frame:GetTop()
status.left = frame:GetLeft()
end
local function sizerseOnMouseDown(this)
this:GetParent():StartSizing("BOTTOMRIGHT")
AceGUI:ClearFocus()
end
local function sizersOnMouseDown(this)
this:GetParent():StartSizing("BOTTOM")
AceGUI:ClearFocus()
end
local function sizereOnMouseDown(this)
this:GetParent():StartSizing("RIGHT")
AceGUI:ClearFocus()
end
local function sizerOnMouseUp(this)
this:GetParent():StopMovingOrSizing()
end
local function SetTitle(self,title)
self.titletext:SetText(title)
end
local function SetStatusText(self,text)
-- self.statustext:SetText(text)
end
local function Hide(self)
self.frame:Hide()
end
local function Show(self)
self.frame:Show()
end
local function OnAcquire(self)
self.frame:SetParent(UIParent)
self.frame:SetFrameStrata("FULLSCREEN_DIALOG")
self:ApplyStatus()
self:EnableResize(true)
self:Show()
end
local function OnRelease(self)
self.status = nil
for k in pairs(self.localstatus) do
self.localstatus[k] = nil
end
end
-- called to set an external table to store status in
local function SetStatusTable(self, status)
assert(type(status) == "table")
self.status = status
self:ApplyStatus()
end
local function ApplyStatus(self)
local status = self.status or self.localstatus
local frame = self.frame
self:SetWidth(status.width or 700)
self:SetHeight(status.height or 500)
if status.top and status.left then
frame:SetPoint("TOP",UIParent,"BOTTOM",0,status.top)
frame:SetPoint("LEFT",UIParent,"LEFT",status.left,0)
else
frame:SetPoint("CENTER",UIParent,"CENTER")
end
end
local function OnWidthSet(self, width)
local content = self.content
local contentwidth = width - 34
if contentwidth < 0 then
contentwidth = 0
end
content:SetWidth(contentwidth)
content.width = contentwidth
end
local function OnHeightSet(self, height)
local content = self.content
local contentheight = height - 57
if contentheight < 0 then
contentheight = 0
end
content:SetHeight(contentheight)
content.height = contentheight
end
local function EnableResize(self, state)
local func = state and "Show" or "Hide"
self.sizer_se[func](self.sizer_se)
self.sizer_s[func](self.sizer_s)
self.sizer_e[func](self.sizer_e)
end
local function Constructor()
local frame = CreateFrame("Frame",nil,UIParent)
local self = {}
self.type = "Window"
self.Hide = Hide
self.Show = Show
self.SetTitle = SetTitle
self.OnRelease = OnRelease
self.OnAcquire = OnAcquire
self.SetStatusText = SetStatusText
self.SetStatusTable = SetStatusTable
self.ApplyStatus = ApplyStatus
self.OnWidthSet = OnWidthSet
self.OnHeightSet = OnHeightSet
self.EnableResize = EnableResize
self.localstatus = {}
self.frame = frame
frame.obj = self
frame:SetWidth(700)
frame:SetHeight(500)
frame:SetPoint("CENTER",UIParent,"CENTER",0,0)
frame:EnableMouse()
frame:SetMovable(true)
frame:SetResizable(true)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
frame:SetScript("OnMouseDown", frameOnMouseDown)
frame:SetScript("OnShow",frameOnShow)
frame:SetScript("OnHide",frameOnClose)
frame:SetMinResize(240,240)
frame:SetToplevel(true)
local titlebg = frame:CreateTexture(nil, "BACKGROUND")
titlebg:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Title-Background]])
titlebg:SetPoint("TOPLEFT", 9, -6)
titlebg:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", -28, -24)
local dialogbg = frame:CreateTexture(nil, "BACKGROUND")
dialogbg:SetTexture([[Interface\Tooltips\UI-Tooltip-Background]])
dialogbg:SetPoint("TOPLEFT", 8, -24)
dialogbg:SetPoint("BOTTOMRIGHT", -6, 8)
dialogbg:SetVertexColor(0, 0, 0, .75)
local topleft = frame:CreateTexture(nil, "BORDER")
topleft:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
topleft:SetWidth(64)
topleft:SetHeight(64)
topleft:SetPoint("TOPLEFT")
topleft:SetTexCoord(0.501953125, 0.625, 0, 1)
local topright = frame:CreateTexture(nil, "BORDER")
topright:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
topright:SetWidth(64)
topright:SetHeight(64)
topright:SetPoint("TOPRIGHT")
topright:SetTexCoord(0.625, 0.75, 0, 1)
local top = frame:CreateTexture(nil, "BORDER")
top:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
top:SetHeight(64)
top:SetPoint("TOPLEFT", topleft, "TOPRIGHT")
top:SetPoint("TOPRIGHT", topright, "TOPLEFT")
top:SetTexCoord(0.25, 0.369140625, 0, 1)
local bottomleft = frame:CreateTexture(nil, "BORDER")
bottomleft:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
bottomleft:SetWidth(64)
bottomleft:SetHeight(64)
bottomleft:SetPoint("BOTTOMLEFT")
bottomleft:SetTexCoord(0.751953125, 0.875, 0, 1)
local bottomright = frame:CreateTexture(nil, "BORDER")
bottomright:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
bottomright:SetWidth(64)
bottomright:SetHeight(64)
bottomright:SetPoint("BOTTOMRIGHT")
bottomright:SetTexCoord(0.875, 1, 0, 1)
local bottom = frame:CreateTexture(nil, "BORDER")
bottom:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
bottom:SetHeight(64)
bottom:SetPoint("BOTTOMLEFT", bottomleft, "BOTTOMRIGHT")
bottom:SetPoint("BOTTOMRIGHT", bottomright, "BOTTOMLEFT")
bottom:SetTexCoord(0.376953125, 0.498046875, 0, 1)
local left = frame:CreateTexture(nil, "BORDER")
left:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
left:SetWidth(64)
left:SetPoint("TOPLEFT", topleft, "BOTTOMLEFT")
left:SetPoint("BOTTOMLEFT", bottomleft, "TOPLEFT")
left:SetTexCoord(0.001953125, 0.125, 0, 1)
local right = frame:CreateTexture(nil, "BORDER")
right:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Border]])
right:SetWidth(64)
right:SetPoint("TOPRIGHT", topright, "BOTTOMRIGHT")
right:SetPoint("BOTTOMRIGHT", bottomright, "TOPRIGHT")
right:SetTexCoord(0.1171875, 0.2421875, 0, 1)
local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", 2, 1)
close:SetScript("OnClick", closeOnClick)
self.closebutton = close
close.obj = self
local titletext = frame:CreateFontString(nil, "ARTWORK")
titletext:SetFontObject(GameFontNormal)
titletext:SetPoint("TOPLEFT", 12, -8)
titletext:SetPoint("TOPRIGHT", -32, -8)
self.titletext = titletext
local title = CreateFrame("Button", nil, frame)
title:SetPoint("TOPLEFT", titlebg)
title:SetPoint("BOTTOMRIGHT", titlebg)
title:EnableMouse()
title:SetScript("OnMouseDown",titleOnMouseDown)
title:SetScript("OnMouseUp", frameOnMouseUp)
self.title = title
local sizer_se = CreateFrame("Frame",nil,frame)
sizer_se:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
sizer_se:SetWidth(25)
sizer_se:SetHeight(25)
sizer_se:EnableMouse()
sizer_se:SetScript("OnMouseDown",sizerseOnMouseDown)
sizer_se:SetScript("OnMouseUp", sizerOnMouseUp)
self.sizer_se = sizer_se
local line1 = sizer_se:CreateTexture(nil, "BACKGROUND")
self.line1 = line1
line1:SetWidth(14)
line1:SetHeight(14)
line1:SetPoint("BOTTOMRIGHT", -8, 8)
line1:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
local x = 0.1 * 14/17
line1:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
local line2 = sizer_se:CreateTexture(nil, "BACKGROUND")
self.line2 = line2
line2:SetWidth(8)
line2:SetHeight(8)
line2:SetPoint("BOTTOMRIGHT", -8, 8)
line2:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
local x = 0.1 * 8/17
line2:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
local sizer_s = CreateFrame("Frame",nil,frame)
sizer_s:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-25,0)
sizer_s:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",0,0)
sizer_s:SetHeight(25)
sizer_s:EnableMouse()
sizer_s:SetScript("OnMouseDown",sizersOnMouseDown)
sizer_s:SetScript("OnMouseUp", sizerOnMouseUp)
self.sizer_s = sizer_s
local sizer_e = CreateFrame("Frame",nil,frame)
sizer_e:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,25)
sizer_e:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,0)
sizer_e:SetWidth(25)
sizer_e:EnableMouse()
sizer_e:SetScript("OnMouseDown",sizereOnMouseDown)
sizer_e:SetScript("OnMouseUp", sizerOnMouseUp)
self.sizer_e = sizer_e
--Container Support
local content = CreateFrame("Frame",nil,frame)
self.content = content
content.obj = self
content:SetPoint("TOPLEFT",frame,"TOPLEFT",12,-32)
content:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-12,13)
AceGUI:RegisterAsContainer(self)
return self
end
AceGUI:RegisterWidgetType(Type,Constructor,Version)
end

179
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button-ElvUI.lua

@ -0,0 +1,179 @@
--[[-----------------------------------------------------------------------------
Button Widget (Modified to change text color on SetDisabled method)
Graphical Button.
-------------------------------------------------------------------------------]]
local Type, Version = "Button-ElvUI", 2
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local _G = _G
local PlaySound, CreateFrame, UIParent = PlaySound, CreateFrame, UIParent
local IsShiftKeyDown = IsShiftKeyDown
-- GLOBALS: GameTooltip, ElvUI
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local dragdropButton
local function lockTooltip()
GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
GameTooltip:SetText(" ")
GameTooltip:Show()
end
local function dragdrop_OnMouseDown(frame, ...)
if frame.obj.dragOnMouseDown then
dragdropButton.mouseDownFrame = frame
dragdropButton:SetText(frame.obj.value or "Unknown")
dragdropButton:SetSize(frame:GetSize())
frame.obj.dragOnMouseDown(frame, ...)
end
end
local function dragdrop_OnMouseUp(frame, ...)
if frame.obj.dragOnMouseUp then
frame:SetAlpha(1)
GameTooltip:Hide()
dragdropButton:Hide()
if dragdropButton.enteredFrame and dragdropButton.enteredFrame ~= frame and dragdropButton.enteredFrame:IsMouseOver() then
frame.obj.dragOnMouseUp(frame, ...)
frame.obj.ActivateMultiControl(frame.obj, ...)
end
dragdropButton.enteredFrame = nil
dragdropButton.mouseDownFrame = nil
end
end
local function dragdrop_OnLeave(frame, ...)
if frame.obj.dragOnLeave then
if dragdropButton.mouseDownFrame then
lockTooltip()
end
if frame == dragdropButton.mouseDownFrame then
frame:SetAlpha(0)
dragdropButton:Show()
frame.obj.dragOnLeave(frame, ...)
end
end
end
local function dragdrop_OnEnter(frame, ...)
if frame.obj.dragOnEnter and dragdropButton:IsShown() then
dragdropButton.enteredFrame = frame
lockTooltip()
frame.obj.dragOnEnter(frame, ...)
end
end
local function dragdrop_OnClick(frame, ...)
local button = ...
if frame.obj.dragOnClick and button == "RightButton" then
frame.obj.dragOnClick(frame, ...)
frame.obj.ActivateMultiControl(frame.obj, ...)
elseif frame.obj.stateSwitchOnClick and (button == "LeftButton") and IsShiftKeyDown() then
frame.obj.stateSwitchOnClick(frame, ...)
frame.obj.ActivateMultiControl(frame.obj, ...)
end
end
local function Button_OnClick(frame, ...)
AceGUI:ClearFocus()
PlaySound("igMainMenuOption")
frame.obj:Fire("OnClick", ...)
end
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- restore default values
self:SetHeight(24)
self:SetWidth(200)
self:SetDisabled(false)
self:SetAutoWidth(false)
self:SetText()
end,
-- ["OnRelease"] = nil,
["SetText"] = function(self, text)
self.text:SetText(text)
if self.autoWidth then
self:SetWidth(self.text:GetStringWidth() + 30)
end
end,
["SetAutoWidth"] = function(self, autoWidth)
self.autoWidth = autoWidth
if self.autoWidth then
self:SetWidth(self.text:GetStringWidth() + 30)
end
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
if disabled then
self.frame:Disable()
self.text:SetTextColor(0.4, 0.4, 0.4)
else
self.frame:Enable()
self.text:SetTextColor(1, 0.82, 0)
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local name = "AceGUI30Button" .. AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Button", name, UIParent, "UIPanelButtonTemplate2")
frame:Hide()
frame:EnableMouse(true)
frame:RegisterForClicks("AnyUp")
frame:SetScript("OnClick", Button_OnClick)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
-- dragdrop
if not dragdropButton then
dragdropButton = CreateFrame("Button", "ElvUIAceGUI30DragDropButton", UIParent, "UIPanelButtonTemplate")
dragdropButton:SetFrameStrata("TOOLTIP")
dragdropButton:SetFrameLevel(5)
dragdropButton:SetPoint('BOTTOM', GameTooltip, "BOTTOM", 0, 10)
dragdropButton:Hide()
ElvUI[1]:GetModule('Skins'):HandleButton(dragdropButton)
end
frame:HookScript("OnClick", dragdrop_OnClick)
frame:HookScript("OnEnter", dragdrop_OnEnter)
frame:HookScript("OnLeave", dragdrop_OnLeave)
frame:HookScript("OnMouseUp", dragdrop_OnMouseUp)
frame:HookScript("OnMouseDown", dragdrop_OnMouseDown)
local text = frame:GetFontString()
text:ClearAllPoints()
text:SetPoint("TOPLEFT", 15, -1)
text:SetPoint("BOTTOMRIGHT", -15, 1)
text:SetJustifyV("MIDDLE")
local widget = {
text = text,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

103
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua

@ -0,0 +1,103 @@
--[[-----------------------------------------------------------------------------
Button Widget
Graphical Button.
-------------------------------------------------------------------------------]]
local Type, Version = "Button", 23
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local _G = _G
local PlaySound, CreateFrame, UIParent = PlaySound, CreateFrame, UIParent
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Button_OnClick(frame, ...)
AceGUI:ClearFocus()
PlaySound("igMainMenuOption")
frame.obj:Fire("OnClick", ...)
end
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
-- restore default values
self:SetHeight(24)
self:SetWidth(200)
self:SetDisabled(false)
self:SetAutoWidth(false)
self:SetText()
end,
-- ["OnRelease"] = nil,
["SetText"] = function(self, text)
self.text:SetText(text)
if self.autoWidth then
self:SetWidth(self.text:GetStringWidth() + 30)
end
end,
["SetAutoWidth"] = function(self, autoWidth)
self.autoWidth = autoWidth
if self.autoWidth then
self:SetWidth(self.text:GetStringWidth() + 30)
end
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
if disabled then
self.frame:Disable()
else
self.frame:Enable()
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local name = "AceGUI30Button" .. AceGUI:GetNextWidgetNum(Type)
local frame = CreateFrame("Button", name, UIParent, "UIPanelButtonTemplate2")
frame:Hide()
frame:EnableMouse(true)
frame:SetScript("OnClick", Button_OnClick)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
local text = frame:GetFontString()
text:ClearAllPoints()
text:SetPoint("TOPLEFT", 15, -1)
text:SetPoint("BOTTOMRIGHT", -15, 1)
text:SetJustifyV("MIDDLE")
local widget = {
text = text,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

295
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua

@ -0,0 +1,295 @@
--[[-----------------------------------------------------------------------------
Checkbox Widget
-------------------------------------------------------------------------------]]
local Type, Version = "CheckBox", 26
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local select, pairs = select, pairs
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: SetDesaturation, GameFontHighlight
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function AlignImage(self)
local img = self.image:GetTexture()
self.text:ClearAllPoints()
if not img then
self.text:SetPoint("LEFT", self.checkbg, "RIGHT")
self.text:SetPoint("RIGHT")
else
self.text:SetPoint("LEFT", self.image, "RIGHT", 1, 0)
self.text:SetPoint("RIGHT")
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function CheckBox_OnMouseDown(frame)
local self = frame.obj
if not self.disabled then
if self.image:GetTexture() then
self.text:SetPoint("LEFT", self.image,"RIGHT", 2, -1)
else
self.text:SetPoint("LEFT", self.checkbg, "RIGHT", 1, -1)
end
end
AceGUI:ClearFocus()
end
local function CheckBox_OnMouseUp(frame)
local self = frame.obj
if not self.disabled then
self:ToggleChecked()
if self.checked then
PlaySound("igMainMenuOptionCheckBoxOn")
else -- for both nil and false (tristate)
PlaySound("igMainMenuOptionCheckBoxOff")
end
self:Fire("OnValueChanged", self.checked)
AlignImage(self)
end
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetType()
self:SetValue(false)
self:SetTriState(nil)
-- height is calculated from the width and required space for the description
self:SetWidth(200)
self:SetImage()
self:SetDisabled(nil)
self:SetDescription(nil)
end,
-- ["OnRelease"] = nil,
["OnWidthSet"] = function(self, width)
if self.desc then
self.desc:SetWidth(width - 30)
if self.desc:GetText() and self.desc:GetText() ~= "" then
self:SetHeight(28 + self.desc:GetStringHeight())
end
end
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
if disabled then
self.frame:Disable()
self.text:SetTextColor(0.5, 0.5, 0.5)
SetDesaturation(self.check, true)
if self.desc then
self.desc:SetTextColor(0.5, 0.5, 0.5)
end
else
self.frame:Enable()
self.text:SetTextColor(1, 1, 1)
if self.tristate and self.checked == nil then
SetDesaturation(self.check, true)
else
SetDesaturation(self.check, false)
end
if self.desc then
self.desc:SetTextColor(1, 1, 1)
end
end
end,
["SetValue"] = function(self, value)
local check = self.check
self.checked = value
if value then
SetDesaturation(check, false)
check:Show()
else
--Nil is the unknown tristate value
if self.tristate and value == nil then
SetDesaturation(check, true)
check:Show()
else
SetDesaturation(check, false)
check:Hide()
end
end
self:SetDisabled(self.disabled)
end,
["GetValue"] = function(self)
return self.checked
end,
["SetTriState"] = function(self, enabled)
self.tristate = enabled
self:SetValue(self:GetValue())
end,
["SetType"] = function(self, type)
local checkbg = self.checkbg
local check = self.check
local highlight = self.highlight
local size
if type == "radio" then
size = 16
checkbg:SetTexture("Interface\\Buttons\\UI-RadioButton")
checkbg:SetTexCoord(0, 0.25, 0, 1)
check:SetTexture("Interface\\Buttons\\UI-RadioButton")
check:SetTexCoord(0.25, 0.5, 0, 1)
check:SetBlendMode("ADD")
highlight:SetTexture("Interface\\Buttons\\UI-RadioButton")
highlight:SetTexCoord(0.5, 0.75, 0, 1)
else
size = 24
checkbg:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
checkbg:SetTexCoord(0, 1, 0, 1)
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
check:SetTexCoord(0, 1, 0, 1)
check:SetBlendMode("BLEND")
highlight:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
highlight:SetTexCoord(0, 1, 0, 1)
end
checkbg:SetHeight(size)
checkbg:SetWidth(size)
end,
["ToggleChecked"] = function(self)
local value = self:GetValue()
if self.tristate then
--cycle in true, nil, false order
if value then
self:SetValue(nil)
elseif value == nil then
self:SetValue(false)
else
self:SetValue(true)
end
else
self:SetValue(not self:GetValue())
end
end,
["SetLabel"] = function(self, label)
self.text:SetText(label)
end,
["SetDescription"] = function(self, desc)
if desc then
if not self.desc then
local desc = self.frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
desc:ClearAllPoints()
desc:SetPoint("TOPLEFT", self.checkbg, "TOPRIGHT", 5, -21)
desc:SetWidth(self.frame.width - 30)
desc:SetJustifyH("LEFT")
desc:SetJustifyV("TOP")
self.desc = desc
end
self.desc:Show()
--self.text:SetFontObject(GameFontNormal)
self.desc:SetText(desc)
self:SetHeight(28 + self.desc:GetStringHeight())
else
if self.desc then
self.desc:SetText("")
self.desc:Hide()
end
--self.text:SetFontObject(GameFontHighlight)
self:SetHeight(24)
end
end,
["SetImage"] = function(self, path, ...)
local image = self.image
image:SetTexture(path)
if image:GetTexture() then
local n = select("#", ...)
if n == 4 or n == 8 then
image:SetTexCoord(...)
else
image:SetTexCoord(0, 1, 0, 1)
end
end
AlignImage(self)
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Button", nil, UIParent)
frame:Hide()
frame:EnableMouse(true)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
frame:SetScript("OnMouseDown", CheckBox_OnMouseDown)
frame:SetScript("OnMouseUp", CheckBox_OnMouseUp)
local checkbg = frame:CreateTexture(nil, "ARTWORK")
checkbg:SetWidth(24)
checkbg:SetHeight(24)
checkbg:SetPoint("TOPLEFT")
checkbg:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
local check = frame:CreateTexture(nil, "OVERLAY")
check:SetAllPoints(checkbg)
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
local text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
text:SetJustifyH("LEFT")
text:SetHeight(18)
text:SetPoint("LEFT", checkbg, "RIGHT")
text:SetPoint("RIGHT")
local highlight = frame:CreateTexture(nil, "HIGHLIGHT")
highlight:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
highlight:SetBlendMode("ADD")
highlight:SetAllPoints(checkbg)
local image = frame:CreateTexture(nil, "OVERLAY")
image:SetHeight(16)
image:SetWidth(16)
image:SetPoint("LEFT", checkbg, "RIGHT", 1, 0)
local widget = {
checkbg = checkbg,
check = check,
text = text,
highlight = highlight,
image = image,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

205
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua

@ -0,0 +1,205 @@
--[[-----------------------------------------------------------------------------
ColorPicker Widget
-------------------------------------------------------------------------------]]
local Type, Version = "ColorPicker-ElvUI", 25
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
-- Lua APIs
local pairs = pairs
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: ColorPickerFrame, OpacitySliderFrame, ColorPPDefault
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function ColorCallback(self, r, g, b, a, isAlpha)
-- this will block an infinite loop from `E.GrabColorPickerValues`
-- which is caused when we set values into the color picker again on `OnValueChanged`
if ColorPickerFrame.noColorCallback then return end
if not self.HasAlpha then
a = 1
end
self:SetColor(r, g, b, a)
if ColorPickerFrame:IsVisible() then
--colorpicker is still open
self:Fire("OnValueChanged", r, g, b, a)
else
--colorpicker is closed, color callback is first, ignore it,
--alpha callback is the final call after it closes so confirm now
if isAlpha then
self:Fire("OnValueConfirmed", r, g, b, a)
end
end
end
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function Control_OnEnter(frame)
frame.obj:Fire("OnEnter")
end
local function Control_OnLeave(frame)
frame.obj:Fire("OnLeave")
end
local function ColorSwatch_OnClick(frame)
ColorPickerFrame:Hide()
local self = frame.obj
if not self.disabled then
ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG")
ColorPickerFrame:SetFrameLevel(frame:GetFrameLevel() + 10)
ColorPickerFrame:SetClampedToScreen(true)
ColorPickerFrame.func = function()
local r, g, b = ColorPickerFrame:GetColorRGB()
local a = 1 - OpacitySliderFrame:GetValue()
ColorCallback(self, r, g, b, a)
end
ColorPickerFrame.hasOpacity = self.HasAlpha
ColorPickerFrame.opacityFunc = function()
local r, g, b = ColorPickerFrame:GetColorRGB()
local a = 1 - OpacitySliderFrame:GetValue()
ColorCallback(self, r, g, b, a, true)
end
local r, g, b, a = self.r, self.g, self.b, self.a
if self.HasAlpha then
ColorPickerFrame.opacity = 1 - (a or 0)
end
ColorPickerFrame:SetColorRGB(r, g, b)
if ColorPPDefault and self.dR and self.dG and self.dB then
local alpha = 1
if self.dA then alpha = 1 - self.dA end
if not ColorPPDefault.colors then ColorPPDefault.colors = {} end
ColorPPDefault.colors.r, ColorPPDefault.colors.g, ColorPPDefault.colors.b, ColorPPDefault.colors.a = self.dR, self.dG, self.dB, alpha
end
ColorPickerFrame.cancelFunc = function()
ColorCallback(self, r, g, b, a, true)
end
ColorPickerFrame:Show()
end
AceGUI:ClearFocus()
end
--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
["OnAcquire"] = function(self)
self:SetHeight(24)
self:SetWidth(200)
self:SetHasAlpha(false)
self:SetColor(0, 0, 0, 1)
self:SetDisabled(nil)
self:SetLabel(nil)
end,
-- ["OnRelease"] = nil,
["SetLabel"] = function(self, text)
self.text:SetText(text)
end,
["SetColor"] = function(self, r, g, b, a, defaultR, defaultG, defaultB, defaultA)
self.r = r
self.g = g
self.b = b
self.a = a or 1
self.dR = defaultR or self.dR
self.dG = defaultG or self.dG
self.dB = defaultB or self.dB
self.dA = defaultA or self.dA
self.colorSwatch:SetVertexColor(r, g, b, a)
end,
["SetHasAlpha"] = function(self, HasAlpha)
self.HasAlpha = HasAlpha
end,
["SetDisabled"] = function(self, disabled)
self.disabled = disabled
if self.disabled then
self.frame:Disable()
self.text:SetTextColor(0.5, 0.5, 0.5)
else
self.frame:Enable()
self.text:SetTextColor(1, 1, 1)
end
end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
local frame = CreateFrame("Button", nil, UIParent)
frame:Hide()
frame:EnableMouse(true)
frame:SetScript("OnEnter", Control_OnEnter)
frame:SetScript("OnLeave", Control_OnLeave)
frame:SetScript("OnClick", ColorSwatch_OnClick)
local colorSwatch = frame:CreateTexture(nil, "OVERLAY")
colorSwatch:SetWidth(19)
colorSwatch:SetHeight(19)
colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch")
colorSwatch:SetPoint("LEFT")
local texture = frame:CreateTexture(nil, "BACKGROUND")
colorSwatch.background = texture
texture:SetWidth(16)
texture:SetHeight(16)
texture:SetTexture(1, 1, 1)
texture:SetPoint("CENTER", colorSwatch)
texture:Show()
local checkers = frame:CreateTexture(nil, "BACKGROUND")
colorSwatch.checkers = checkers
checkers:SetWidth(14)
checkers:SetHeight(14)
checkers:SetTexture("Tileset\\Generic\\Checkers")
checkers:SetTexCoord(.25, 0, 0.5, .25)
checkers:SetDesaturated(true)
checkers:SetVertexColor(1, 1, 1, 0.75)
checkers:SetPoint("CENTER", colorSwatch)
checkers:Show()
local text = frame:CreateFontString(nil,"OVERLAY","GameFontHighlight")
text:SetHeight(24)
text:SetJustifyH("LEFT")
text:SetTextColor(1, 1, 1)
text:SetPoint("LEFT", colorSwatch, "RIGHT", 2, 0)
text:SetPoint("RIGHT")
--local highlight = frame:CreateTexture(nil, "HIGHLIGHT")
--highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
--highlight:SetBlendMode("ADD")
--highlight:SetAllPoints(frame)
local widget = {
colorSwatch = colorSwatch,
text = text,
frame = frame,
type = Type
}
for method, func in pairs(methods) do
widget[method] = func
end
return AceGUI:RegisterAsWidget(widget)
end
AceGUI:RegisterWidgetType(Type, Constructor, Version)

471
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua

@ -0,0 +1,471 @@
--[[ $Id$ ]]--
local AceGUI = LibStub("AceGUI-3.0")
-- Lua APIs
local select, assert = select, assert
-- WoW APIs
local PlaySound = PlaySound
local CreateFrame = CreateFrame
local function fixlevels(parent,...)
local i = 1
local child = select(i, ...)
while child do
child:SetFrameLevel(parent:GetFrameLevel()+1)
fixlevels(child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
local function fixstrata(strata, parent, ...)
local i = 1
local child = select(i, ...)
parent:SetFrameStrata(strata)
while child do
fixstrata(strata, child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
-- ItemBase is the base "class" for all dropdown items.
-- Each item has to use ItemBase.Create(widgetType) to
-- create an initial 'self' value.
-- ItemBase will add common functions and ui event handlers.
-- Be sure to keep basic usage when you override functions.
local ItemBase = {
-- NOTE: The ItemBase version is added to each item's version number
-- to ensure proper updates on ItemBase changes.
-- Use at least 1000er steps.
version = 1000,
counter = 0,
}
function ItemBase.Frame_OnEnter(this)
local self = this.obj
if self.useHighlight then
self.highlight:Show()
end
self:Fire("OnEnter")
if self.specialOnEnter then
self.specialOnEnter(self)
end
end
function ItemBase.Frame_OnLeave(this)
local self = this.obj
self.highlight:Hide()
self:Fire("OnLeave")
if self.specialOnLeave then
self.specialOnLeave(self)
end
end
-- exported, AceGUI callback
function ItemBase.OnAcquire(self)
self.frame:SetToplevel(true)
self.frame:SetFrameStrata("FULLSCREEN_DIALOG")
end
-- exported, AceGUI callback
function ItemBase.OnRelease(self)
self:SetDisabled(false)
self.pullout = nil
self.frame:SetParent(nil)
self.frame:ClearAllPoints()
self.frame:Hide()
end
-- exported
-- NOTE: this is called by a Dropdown-Pullout.
-- Do not call this method directly
function ItemBase.SetPullout(self, pullout)
self.pullout = pullout
self.frame:SetParent(nil)
self.frame:SetParent(pullout.itemFrame)
self.parent = pullout.itemFrame
fixlevels(pullout.itemFrame, pullout.itemFrame:GetChildren())
end
-- exported
function ItemBase.SetText(self, text)
self.text:SetText(text or "")
end
-- exported
function ItemBase.GetText(self)
return self.text:GetText()
end
-- exported
function ItemBase.SetPoint(self, ...)
self.frame:SetPoint(...)
end
-- exported
function ItemBase.Show(self)
self.frame:Show()
end
-- exported
function ItemBase.Hide(self)
self.frame:Hide()
end
-- exported
function ItemBase.SetDisabled(self, disabled)
self.disabled = disabled
if disabled then
self.useHighlight = false
self.text:SetTextColor(.5, .5, .5)
else
self.useHighlight = true
self.text:SetTextColor(1, 1, 1)
end
end
-- exported
-- NOTE: this is called by a Dropdown-Pullout.
-- Do not call this method directly
function ItemBase.SetOnLeave(self, func)
self.specialOnLeave = func
end
-- exported
-- NOTE: this is called by a Dropdown-Pullout.
-- Do not call this method directly
function ItemBase.SetOnEnter(self, func)
self.specialOnEnter = func
end
function ItemBase.Create(type)
-- NOTE: Most of the following code is copied from AceGUI-3.0/Dropdown widget
local count = AceGUI:GetNextWidgetNum(type)
local frame = CreateFrame("Button", "AceGUI30DropDownItem"..count)
local self = {}
self.frame = frame
frame.obj = self
self.type = type
self.useHighlight = true
frame:SetHeight(17)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
local text = frame:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
text:SetTextColor(1,1,1)
text:SetJustifyH("LEFT")
text:SetPoint("TOPLEFT",frame,"TOPLEFT",18,0)
text:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-8,0)
self.text = text
local highlight = frame:CreateTexture(nil, "OVERLAY")
highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
highlight:SetBlendMode("ADD")
highlight:SetHeight(14)
highlight:ClearAllPoints()
highlight:SetPoint("RIGHT",frame,"RIGHT",-3,0)
highlight:SetPoint("LEFT",frame,"LEFT",5,0)
highlight:Hide()
self.highlight = highlight
local check = frame:CreateTexture("OVERLAY")
check:SetWidth(16)
check:SetHeight(16)
check:SetPoint("LEFT",frame,"LEFT",3,-1)
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
check:Hide()
self.check = check
local sub = frame:CreateTexture("OVERLAY")
sub:SetWidth(16)
sub:SetHeight(16)
sub:SetPoint("RIGHT",frame,"RIGHT",-3,-1)
sub:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow")
sub:Hide()
self.sub = sub
frame:SetScript("OnEnter", ItemBase.Frame_OnEnter)
frame:SetScript("OnLeave", ItemBase.Frame_OnLeave)
self.OnAcquire = ItemBase.OnAcquire
self.OnRelease = ItemBase.OnRelease
self.SetPullout = ItemBase.SetPullout
self.GetText = ItemBase.GetText
self.SetText = ItemBase.SetText
self.SetDisabled = ItemBase.SetDisabled
self.SetPoint = ItemBase.SetPoint
self.Show = ItemBase.Show
self.Hide = ItemBase.Hide
self.SetOnLeave = ItemBase.SetOnLeave
self.SetOnEnter = ItemBase.SetOnEnter
return self
end
-- Register a dummy LibStub library to retrieve the ItemBase, so other addons can use it.
local IBLib = LibStub:NewLibrary("AceGUI-3.0-DropDown-ItemBase", ItemBase.version)
if IBLib then
IBLib.GetItemBase = function() return ItemBase end
end
--[[
Template for items:
-- Item:
--
do
local widgetType = "Dropdown-Item-"
local widgetVersion = 1
local function Constructor()
local self = ItemBase.Create(widgetType)
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion + ItemBase.version)
end
--]]
-- Item: Header
-- A single text entry.
-- Special: Different text color and no highlight
do
local widgetType = "Dropdown-Item-Header"
local widgetVersion = 1
local function OnEnter(this)
local self = this.obj
self:Fire("OnEnter")
if self.specialOnEnter then
self.specialOnEnter(self)
end
end
local function OnLeave(this)
local self = this.obj
self:Fire("OnLeave")
if self.specialOnLeave then
self.specialOnLeave(self)
end
end
-- exported, override
local function SetDisabled(self, disabled)
ItemBase.SetDisabled(self, disabled)
if not disabled then
self.text:SetTextColor(1, 1, 0)
end
end
local function Constructor()
local self = ItemBase.Create(widgetType)
self.SetDisabled = SetDisabled
self.frame:SetScript("OnEnter", OnEnter)
self.frame:SetScript("OnLeave", OnLeave)
self.text:SetTextColor(1, 1, 0)
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion + ItemBase.version)
end
-- Item: Execute
-- A simple button
do
local widgetType = "Dropdown-Item-Execute"
local widgetVersion = 1
local function Frame_OnClick(this, button)
local self = this.obj
if self.disabled then return end
self:Fire("OnClick")
if self.pullout then
self.pullout:Close()
end
end
local function Constructor()
local self = ItemBase.Create(widgetType)
self.frame:SetScript("OnClick", Frame_OnClick)
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion + ItemBase.version)
end
-- Item: Toggle
-- Some sort of checkbox for dropdown menus.
-- Does not close the pullout on click.
do
local widgetType = "Dropdown-Item-Toggle"
local widgetVersion = 3
local function UpdateToggle(self)
if self.value then
self.check:Show()
else
self.check:Hide()
end
end
local function OnRelease(self)
ItemBase.OnRelease(self)
self:SetValue(nil)
end
local function Frame_OnClick(this, button)
local self = this.obj
if self.disabled then return end
self.value = not self.value
if self.value then
PlaySound("igMainMenuOptionCheckBoxOn")
else
PlaySound("igMainMenuOptionCheckBoxOff")
end
UpdateToggle(self)
self:Fire("OnValueChanged", self.value)
end
-- exported
local function SetValue(self, value)
self.value = value
UpdateToggle(self)
end
-- exported
local function GetValue(self)
return self.value
end
local function Constructor()
local self = ItemBase.Create(widgetType)
self.frame:SetScript("OnClick", Frame_OnClick)
self.SetValue = SetValue
self.GetValue = GetValue
self.OnRelease = OnRelease
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion + ItemBase.version)
end
-- Item: Menu
-- Shows a submenu on mouse over
-- Does not close the pullout on click
do
local widgetType = "Dropdown-Item-Menu"
local widgetVersion = 2
local function OnEnter(this)
local self = this.obj
self:Fire("OnEnter")
if self.specialOnEnter then
self.specialOnEnter(self)
end
self.highlight:Show()
if not self.disabled and self.submenu then
self.submenu:Open("TOPLEFT", self.frame, "TOPRIGHT", self.pullout:GetRightBorderWidth(), 0, self.frame:GetFrameLevel() + 100)
end
end
local function OnHide(this)
local self = this.obj
if self.submenu then
self.submenu:Close()
end
end
-- exported
local function SetMenu(self, menu)
assert(menu.type == "Dropdown-Pullout")
self.submenu = menu
end
-- exported
local function CloseMenu(self)
self.submenu:Close()
end
local function Constructor()
local self = ItemBase.Create(widgetType)
self.sub:Show()
self.frame:SetScript("OnEnter", OnEnter)
self.frame:SetScript("OnHide", OnHide)
self.SetMenu = SetMenu
self.CloseMenu = CloseMenu
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion + ItemBase.version)
end
-- Item: Separator
-- A single line to separate items
do
local widgetType = "Dropdown-Item-Separator"
local widgetVersion = 1
-- exported, override
local function SetDisabled(self, disabled)
ItemBase.SetDisabled(self, disabled)
self.useHighlight = false
end
local function Constructor()
local self = ItemBase.Create(widgetType)
self.SetDisabled = SetDisabled
local line = self.frame:CreateTexture(nil, "OVERLAY")
line:SetHeight(1)
line:SetTexture(.5, .5, .5)
line:SetPoint("LEFT", self.frame, "LEFT", 10, 0)
line:SetPoint("RIGHT", self.frame, "RIGHT", -10, 0)
self.text:Hide()
self.useHighlight = false
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion + ItemBase.version)
end

771
TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua

@ -0,0 +1,771 @@
--[[ $Id$ ]]--
local AceGUI = LibStub("AceGUI-3.0")
-- Lua APIs
local min, max, floor = math.min, math.max, math.floor
local select, pairs, ipairs, type, tostring = select, pairs, ipairs, type, tostring
local tsort, tonumber = table.sort, tonumber
-- WoW APIs
local PlaySound = PlaySound
local UIParent, CreateFrame = UIParent, CreateFrame
local _G = _G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: CLOSE
local function fixlevels(parent,...)
local i = 1
local child = select(i, ...)
while child do
child:SetFrameLevel(parent:GetFrameLevel()+1)
fixlevels(child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
local function fixstrata(strata, parent, ...)
local i = 1
local child = select(i, ...)
parent:SetFrameStrata(strata)
while child do
fixstrata(strata, child, child:GetChildren())
i = i + 1
child = select(i, ...)
end
end
do
local widgetType = "Dropdown-Pullout"
local widgetVersion = 3
--[[ Static data ]]--
local backdrop = {
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
edgeSize = 32,
tileSize = 32,
tile = true,
insets = { left = 11, right = 12, top = 12, bottom = 11 },
}
local sliderBackdrop = {
bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
tile = true, tileSize = 8, edgeSize = 8,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
}
local defaultWidth = 200
local defaultMaxHeight = 600
--[[ UI Event Handlers ]]--
-- HACK: This should be no part of the pullout, but there
-- is no other 'clean' way to response to any item-OnEnter
-- Used to close Submenus when an other item is entered
local function OnEnter(item)
local self = item.pullout
for k, v in ipairs(self.items) do
if v.CloseMenu and v ~= item then
v:CloseMenu()
end
end
end
-- See the note in Constructor() for each scroll related function
local function OnMouseWheel(this, value)
this.obj:MoveScroll(value)
end
local function OnScrollValueChanged(this, value)
this.obj:SetScroll(value)
end
local function OnSizeChanged(this)
this.obj:FixScroll()
end
--[[ Exported methods ]]--
-- exported
local function SetScroll(self, value)
local status = self.scrollStatus
local frame, child = self.scrollFrame, self.itemFrame
local height, viewheight = frame:GetHeight(), child:GetHeight()
local offset
if height > viewheight then
offset = 0
else
offset = floor((viewheight - height) / 1000 * value)
end
child:ClearAllPoints()
child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset)
child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", self.slider:IsShown() and -12 or 0, offset)
status.offset = offset
status.scrollvalue = value
end
-- exported
local function MoveScroll(self, value)
local status = self.scrollStatus
local frame, child = self.scrollFrame, self.itemFrame
local height, viewheight = frame:GetHeight(), child:GetHeight()
if height > viewheight then
self.slider:Hide()
else
self.slider:Show()
local diff = height - viewheight
local delta = 1
if value < 0 then
delta = -1
end
self.slider:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000))
end
end
-- exported
local function FixScroll(self)
local status = self.scrollStatus
local frame, child = self.scrollFrame, self.itemFrame
local height, viewheight = frame:GetHeight(), child:GetHeight()
local offset = status.offset or 0
if viewheight < height then
self.slider:Hide()
child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, offset)
self.slider:SetValue(0)
else
self.slider:Show()
local value = (offset / (viewheight - height) * 1000)
if value > 1000 then value = 1000 end
self.slider:SetValue(value)
self:SetScroll(value)
if value < 1000 then
child:ClearAllPoints()
child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset)
child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, offset)
status.offset = offset
end
end
end
-- exported, AceGUI callback
local function OnAcquire(self)
self.frame:SetParent(UIParent)
--self.itemFrame:SetToplevel(true)
end
-- exported, AceGUI callback
local function OnRelease(self)
self:Clear()
self.frame:ClearAllPoints()
self.frame:Hide()
end
-- exported
local function AddItem(self, item)
self.items[#self.items + 1] = item
local h = #self.items * 16
self.itemFrame:SetHeight(h)
self.frame:SetHeight(min(h + 34, self.maxHeight)) -- +34: 20 for scrollFrame placement (10 offset) and +14 for item placement
item.frame:SetPoint("LEFT", self.itemFrame, "LEFT")
item.frame:SetPoint("RIGHT", self.itemFrame, "RIGHT")
item:SetPullout(self)
item:SetOnEnter(OnEnter)
end
-- exported
local function Open(self, point, relFrame, relPoint, x, y)
local items = self.items
local frame = self.frame
local itemFrame = self.itemFrame
frame:SetPoint(point, relFrame, relPoint, x, y)
local height = 8
for i, item in pairs(items) do
if i == 1 then
item:SetPoint("TOP", itemFrame, "TOP", 0, -2)
else
item:SetPoint("TOP", items[i-1].frame, "BOTTOM", 0, 1)
end
item:Show()
height = height + 16
end
itemFrame:SetHeight(height)
fixstrata("TOOLTIP", frame, frame:GetChildren())
frame:Show()
self:Fire("OnOpen")
end
-- exported
local function Close(self)
self.frame:Hide()
self:Fire("OnClose")
end
-- exported
local function Clear(self)
local items = self.items
for i, item in pairs(items) do
AceGUI:Release(item)
items[i] = nil
end
end
-- exported
local function IterateItems(self)
return ipairs(self.items)
end
-- exported
local function SetHideOnLeave(self, val)
self.hideOnLeave = val
end
-- exported
local function SetMaxHeight(self, height)
self.maxHeight = height or defaultMaxHeight
if self.frame:GetHeight() > height then
self.frame:SetHeight(height)
elseif (self.itemFrame:GetHeight() + 34) < height then
self.frame:SetHeight(self.itemFrame:GetHeight() + 34) -- see :AddItem
end
end
-- exported
local function GetRightBorderWidth(self)
return 6 + (self.slider:IsShown() and 12 or 0)
end
-- exported
local function GetLeftBorderWidth(self)
return 6
end
--[[ Constructor ]]--
local function Constructor()
local count = AceGUI:GetNextWidgetNum(widgetType)
local frame = CreateFrame("Frame", "AceGUI30Pullout"..count, UIParent)
local self = {}
self.count = count
self.type = widgetType
self.frame = frame
frame.obj = self
self.OnAcquire = OnAcquire
self.OnRelease = OnRelease
self.AddItem = AddItem
self.Open = Open
self.Close = Close
self.Clear = Clear
self.IterateItems = IterateItems
self.SetHideOnLeave = SetHideOnLeave
self.SetScroll = SetScroll
self.MoveScroll = MoveScroll
self.FixScroll = FixScroll
self.SetMaxHeight = SetMaxHeight
self.GetRightBorderWidth = GetRightBorderWidth
self.GetLeftBorderWidth = GetLeftBorderWidth
self.items = {}
self.scrollStatus = {
scrollvalue = 0,
}
self.maxHeight = defaultMaxHeight
frame:SetBackdrop(backdrop)
frame:SetBackdropColor(0, 0, 0)
frame:SetFrameStrata("FULLSCREEN_DIALOG")
frame:SetClampedToScreen(true)
frame:SetWidth(defaultWidth)
frame:SetHeight(self.maxHeight)
--frame:SetToplevel(true)
-- NOTE: The whole scroll frame code is copied from the AceGUI-3.0 widget ScrollFrame
local scrollFrame = CreateFrame("ScrollFrame", nil, frame)
local itemFrame = CreateFrame("Frame", nil, scrollFrame)
self.scrollFrame = scrollFrame
self.itemFrame = itemFrame
scrollFrame.obj = self
itemFrame.obj = self
local slider = CreateFrame("Slider", "AceGUI30PulloutScrollbar"..count, scrollFrame)
slider:SetOrientation("VERTICAL")
slider:SetHitRectInsets(0, 0, -10, 0)
slider:SetBackdrop(sliderBackdrop)
slider:SetWidth(8)
slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical")
slider:SetFrameStrata("FULLSCREEN_DIALOG")
self.slider = slider
slider.obj = self
scrollFrame:SetScrollChild(itemFrame)
scrollFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -12)
scrollFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -6, 12)
scrollFrame:EnableMouseWheel(true)
scrollFrame:SetScript("OnMouseWheel", OnMouseWheel)
scrollFrame:SetScript("OnSizeChanged", OnSizeChanged)
scrollFrame:SetToplevel(true)
scrollFrame:SetFrameStrata("FULLSCREEN_DIALOG")
itemFrame:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", 0, 0)
itemFrame:SetPoint("TOPRIGHT", scrollFrame, "TOPRIGHT", -12, 0)
itemFrame:SetHeight(400)
itemFrame:SetToplevel(true)
itemFrame:SetFrameStrata("FULLSCREEN_DIALOG")
slider:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", -16, 0)
slider:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", -16, 0)
slider:SetScript("OnValueChanged", OnScrollValueChanged)
slider:SetMinMaxValues(0, 1000)
slider:SetValueStep(1)
slider:SetValue(0)
scrollFrame:Show()
itemFrame:Show()
slider:Hide()
self:FixScroll()
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion)
end
do
local widgetType = "Dropdown"
local widgetVersion = 34
--[[ Static data ]]--
--[[ UI event handler ]]--
local function Control_OnEnter(this)
this.obj.button:LockHighlight()
this.obj:Fire("OnEnter")
end
local function Control_OnLeave(this)
this.obj.button:UnlockHighlight()
this.obj:Fire("OnLeave")
end
local function Dropdown_OnHide(this)
local self = this.obj
if self.open then
self.pullout:Close()
end
end
local function Dropdown_TogglePullout(this)
local self = this.obj
PlaySound("igMainMenuOptionCheckBoxOn") -- missleading name, but the Blizzard code uses this sound
if self.open then
self.open = nil
self.pullout:Close()
AceGUI:ClearFocus()
else
self.open = true
self.pullout:SetWidth(self.pulloutWidth or self.frame:GetWidth())
self.pullout:Open("TOPLEFT", self.frame, "BOTTOMLEFT", 0, self.label:IsShown() and -2 or 0)
AceGUI:SetFocus(self)
end
end
local function OnPulloutOpen(this)
local self = this.userdata.obj
local value = self.value
if not self.multiselect then
for i, item in this:IterateItems() do
item:SetValue(item.userdata.value == value)
end
end
self.open = true
self:Fire("OnOpened")
end
local function OnPulloutClose(this)
local self = this.userdata.obj
self.open = nil
self:Fire("OnClosed")
end
local function ShowMultiText(self)
local text
for i, widget in self.pullout:IterateItems() do
if widget.type == "Dropdown-Item-Toggle" then
if widget:GetValue() then
if text then
text = text..", "..widget:GetText()
else
text = widget:GetText()
end
end
end
end
self:SetText(text)
end
local function OnItemValueChanged(this, event, checked)
local self = this.userdata.obj
if self.multiselect then
self:Fire("OnValueChanged", this.userdata.value, checked)
ShowMultiText(self)
else
if checked then
self:SetValue(this.userdata.value)
self:Fire("OnValueChanged", this.userdata.value)
else
this:SetValue(true)
end
if self.open then
self.pullout:Close()
end
end
end
--[[ Exported methods ]]--
-- exported, AceGUI callback
local function OnAcquire(self)
local pullout = AceGUI:Create("Dropdown-Pullout")
self.pullout = pullout
pullout.userdata.obj = self
pullout:SetCallback("OnClose", OnPulloutClose)
pullout:SetCallback("OnOpen", OnPulloutOpen)
self.pullout.frame:SetFrameLevel(self.frame:GetFrameLevel() + 1)
fixlevels(self.pullout.frame, self.pullout.frame:GetChildren())
self:SetHeight(44)
self:SetWidth(200)
self:SetLabel()
self:SetPulloutWidth(nil)
end
-- exported, AceGUI callback
local function OnRelease(self)
if self.open then
self.pullout:Close()
end
AceGUI:Release(self.pullout)
self.pullout = nil
self:SetText("")
self:SetDisabled(false)
self:SetMultiselect(false)
self.value = nil
self.list = nil
self.open = nil
self.hasClose = nil
self.frame:ClearAllPoints()
self.frame:Hide()
end
-- exported
local function SetDisabled(self, disabled)
self.disabled = disabled
if disabled then
self.text:SetTextColor(0.5,0.5,0.5)
self.button:Disable()
self.button_cover:Disable()
self.label:SetTextColor(0.5,0.5,0.5)
else
self.button:Enable()
self.button_cover:Enable()
self.label:SetTextColor(1,.82,0)
self.text:SetTextColor(1,1,1)
end
end
-- exported
local function ClearFocus(self)
if self.open then
self.pullout:Close()
end
end
-- exported
local function SetText(self, text)
self.text:SetText(text or "")
end
-- exported
local function SetLabel(self, text)
if text and text ~= "" then
self.label:SetText(text)
self.label:Show()
self.dropdown:SetPoint("TOPLEFT",self.frame,"TOPLEFT",-15,-14)
self:SetHeight(40)
self.alignoffset = 26
else
self.label:SetText("")
self.label:Hide()
self.dropdown:SetPoint("TOPLEFT",self.frame,"TOPLEFT",-15,0)
self:SetHeight(26)
self.alignoffset = 12
end
end
-- exported
local function SetValue(self, value)
if self.list then
self:SetText(self.list[value] or "")
end
self.value = value
end
-- exported
local function GetValue(self)
return self.value
end
-- exported
local function SetItemValue(self, item, value)
if not self.multiselect then return end
for i, widget in self.pullout:IterateItems() do
if widget.userdata.value == item then
if widget.SetValue then
widget:SetValue(value)
end
end
end
ShowMultiText(self)
end
-- exported
local function SetItemDisabled(self, item, disabled)
for i, widget in self.pullout:IterateItems() do
if widget.userdata.value == item then
widget:SetDisabled(disabled)
end
end
end
local function AddListItem(self, value, text, itemType)
if not itemType then itemType = "Dropdown-Item-Toggle" end
local exists = AceGUI:GetWidgetVersion(itemType)
if not exists then error(("The given item type, %q, does not exist within AceGUI-3.0"):format(tostring(itemType)), 2) end
local item = AceGUI:Create(itemType)
item:SetText(text)
item.userdata.obj = self
item.userdata.value = value
item:SetCallback("OnValueChanged", OnItemValueChanged)
self.pullout:AddItem(item)
end
local function AddCloseButton(self)
if not self.hasClose then
local close = AceGUI:Create("Dropdown-Item-Execute")
close:SetText(CLOSE)
self.pullout:AddItem(close)
self.hasClose = true
end
end
-- exported
local sortlist = {}
local function sortTbl(x,y)
local num1, num2 = tonumber(x), tonumber(y)
if num1 and num2 then -- numeric comparison, either two numbers or numeric strings
return num1 < num2
else -- compare everything else tostring'ed
return tostring(x) < tostring(y)
end
end
-- these were added by ElvUI
local sortStr1, sortStr2 = "%((%d+)%)", "%[(%d+)]"
local sortValue = function(a,b)
if a and b and (a[2] and b[2]) then
local a2 = tonumber(a[2]:match(sortStr1) or a[2]:match(sortStr2))
local b2 = tonumber(b[2]:match(sortStr1) or b[2]:match(sortStr2))
if a2 and b2 and (a2 ~= b2) then
return a2 < b2 -- try to sort by the number inside of brackets if we can
end
return a[2] < b[2]
end
end
local function SetList(self, list, order, itemType, sortByValue)
self.list = list
self.pullout:Clear()
self.hasClose = nil
if not list then return end
if type(order) ~= "table" then
if sortByValue then -- added by ElvUI
for k, v in pairs(list) do
sortlist[#sortlist + 1] = {k,v}
end
tsort(sortlist, sortValue)
for i, sortedList in ipairs(sortlist) do
AddListItem(self, sortedList[1], sortedList[2], itemType)
sortlist[i] = nil
end
else -- this is the default way (unchanged by ElvUI)
for v in pairs(list) do
sortlist[#sortlist + 1] = v
end
tsort(sortlist, sortTbl)
for i, key in ipairs(sortlist) do
AddListItem(self, key, list[key], itemType)
sortlist[i] = nil
end
end
else
for i, key in ipairs(order) do
AddListItem(self, key, list[key], itemType)
end
end
if self.multiselect then
ShowMultiText(self)
AddCloseButton(self)
end
end
-- exported
local function AddItem(self, value, text, itemType)
if self.list then
self.list[value] = text
AddListItem(self, value, text, itemType)
end
end
-- exported
local function SetMultiselect(self, multi)
self.multiselect = multi
if multi then
ShowMultiText(self)
AddCloseButton(self)
end
end
-- exported
local function GetMultiselect(self)
return self.multiselect
end
local function SetPulloutWidth(self, width)
self.pulloutWidth = width
end
--[[ Constructor ]]--
local function Constructor()
local count = AceGUI:GetNextWidgetNum(widgetType)
local frame = CreateFrame("Frame", nil, UIParent)
local dropdown = CreateFrame("Frame", "AceGUI30DropDown"..count, frame, "UIDropDownMenuTemplate")
local self = {}
self.type = widgetType
self.frame = frame
self.dropdown = dropdown
self.count = count
frame.obj = self
dropdown.obj = self
self.OnRelease = OnRelease
self.OnAcquire = OnAcquire
self.ClearFocus = ClearFocus
self.SetText = SetText
self.SetValue = SetValue
self.GetValue = GetValue
self.SetList = SetList
self.SetLabel = SetLabel
self.SetDisabled = SetDisabled
self.AddItem = AddItem
self.SetMultiselect = SetMultiselect
self.GetMultiselect = GetMultiselect
self.SetItemValue = SetItemValue
self.SetItemDisabled = SetItemDisabled
self.SetPulloutWidth = SetPulloutWidth
self.alignoffset = 26
frame:SetScript("OnHide",Dropdown_OnHide)
dropdown:ClearAllPoints()
dropdown:SetPoint("TOPLEFT",frame,"TOPLEFT",-15,0)
dropdown:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",17,0)
dropdown:SetScript("OnHide", nil)
local left = _G[dropdown:GetName() .. "Left"]
local middle = _G[dropdown:GetName() .. "Middle"]
local right = _G[dropdown:GetName() .. "Right"]
middle:ClearAllPoints()
right:ClearAllPoints()
middle:SetPoint("LEFT", left, "RIGHT", 0, 0)
middle:SetPoint("RIGHT", right, "LEFT", 0, 0)
right:SetPoint("TOPRIGHT", dropdown, "TOPRIGHT", 0, 17)
local button = _G[dropdown:GetName() .. "Button"]
self.button = button
button.obj = self
button:SetScript("OnEnter",Control_OnEnter)
button:SetScript("OnLeave",Control_OnLeave)
button:SetScript("OnClick",Dropdown_TogglePullout)
local button_cover = CreateFrame("BUTTON",nil,self.frame)
self.button_cover = button_cover
button_cover.obj = self
button_cover:SetPoint("TOPLEFT",self.frame,"BOTTOMLEFT",0,25)
button_cover:SetPoint("BOTTOMRIGHT",self.frame,"BOTTOMRIGHT")
button_cover:SetScript("OnEnter",Control_OnEnter)
button_cover:SetScript("OnLeave",Control_OnLeave)
button_cover:SetScript("OnClick",Dropdown_TogglePullout)
local text = _G[dropdown:GetName() .. "Text"]
self.text = text
text.obj = self
text:ClearAllPoints()
text:SetPoint("RIGHT", right, "RIGHT" ,-43, 2)
text:SetPoint("LEFT", left, "LEFT", 25, 2)
local label = frame:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
label:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,0)
label:SetJustifyH("LEFT")
label:SetHeight(18)
label:Hide()
self.label = label
AceGUI:RegisterAsWidget(self)
return self
end
AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion)
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save