386 changed files with 93729 additions and 2 deletions
@ -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. |
||||||
@ -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 |
||||||
@ -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"}, |
||||||
|
-- }, |
||||||
|
-- }, |
||||||
|
-- }, |
||||||
|
-- }, |
||||||
|
}, |
||||||
|
} |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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! |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
File diff suppressed because it is too large
Load Diff
@ -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 |
||||||
@ -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 |
||||||
File diff suppressed because it is too large
Load Diff
@ -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 |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
File diff suppressed because it is too large
Load Diff
@ -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 |
||||||
|
} |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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 |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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@]===] |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
|
]] |
||||||
|
|
||||||
|
|
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
File diff suppressed because it is too large
Load Diff
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
@ -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 |
||||||
@ -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> |
||||||
File diff suppressed because it is too large
Load Diff
@ -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> |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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 |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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 |
||||||
@ -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…
Reference in new issue