/
AuctionStats = LibStub("AceAddon-3.0"):NewAddon("AuctionStats", "AceEvent-3.0") |
local L = AuctionStatLocals |
local MAX_DATE_ROWS = 23 |
local MAX_DATA_ROWS = 19 |
local MAX_DATA_COLUMNS = 4 |
local monthTable = {day = 1, hour = 0} |
-- Total data per day/month including the item statistics related to that |
local auctionData = {} |
-- Quick sortable tables we can use |
local auctionDisplay = {} |
function AuctionStats:OnInitialize() |
self.defaults = { |
profile = { |
}, |
} |
self.db = LibStub:GetLibrary("AceDB-3.0"):New("AuctionStatsDB", self.defaults) |
-- No data found, default to current char |
if( not self.db.profile.gatherData ) then |
self.db.profile.gatherData = {[string.format("%s:%s", GetRealmName(), UnitName("player"))] = true} |
end |
SLASH_AUCTIONSTATS1 = "/auctionstats" |
SLASH_AUCTIONSTATS2 = "/as" |
SlashCmdList["AUCTIONSTATS"] = function(msg) |
AuctionStats:CreateGUI() |
AuctionStats.frame:Show() |
end |
end |
local function sortTime(a, b) |
return a < b |
end |
local function getTime(currentTime, day, hour) |
monthTable.year = date("%Y", currentTime) |
monthTable.month = date("%m", currentTime) |
monthTable.day = day or date("%d", currentTime) |
monthTable.hour = hour or 0 |
return time(monthTable) |
end |
local function hideTooltip() |
GameTooltip:Hide() |
end |
local function showTooltip(self) |
if( self.tooltip ) then |
if( not self.button or not self.button:IsVisible() ) then |
GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT") |
else |
GameTooltip:SetOwner(self.button, "ANCHOR_TOPLEFT") |
end |
GameTooltip:SetText(self.tooltip, nil, nil, nil, nil, 1) |
end |
end |
local function monthToggle(self) |
AuctionStats.frame.dateKey = nil |
if( AuctionStats.frame.monthKey == self.monthKey ) then |
AuctionStats.frame.monthKey = nil |
else |
AuctionStats.frame.dateKey = self.monthKey |
AuctionStats.frame.monthKey = self.monthKey |
end |
AuctionStats:UpdateBrowseGUI() |
AuctionStats:ViewBreakdown() |
end |
local function showDisplayGUI(self) |
local month = getTime(self.dateKey, 1, 0) |
if( AuctionStats.frame.dateKey == self.dateKey ) then |
if( self.dateKey ~= month ) then |
AuctionStats.frame.dateKey = month |
AuctionStats.frame.monthKey = month |
else |
AuctionStats.frame.dateKey = nil |
AuctionStats.frame.monthKey = nil |
end |
else |
AuctionStats.frame.dateKey = self.dateKey |
AuctionStats.frame.monthKey = month |
AuctionStats.frame.resortList = true |
end |
AuctionStats:UpdateBrowseGUI() |
AuctionStats:ViewBreakdown() |
end |
-- Quick merge function to add the data from one table to the main one |
local mergeKeys = {"totalSold", "totalMade", "totalDeposit", "totalFee", "totalSpent", "totalBought"} |
local function mergeDataTables(to, from) |
for _, key in pairs(mergeKeys) do |
if( from[key] ) then |
to[key] = (to[key] or 0) + from[key] |
end |
end |
end |
function AuctionStats:ParseData() |
auctionData = {} |
for k in pairs(auctionDisplay) do auctionDisplay[k] = nil end |
for charID in pairs(self.db.profile.gatherData) do |
local server, name = string.split(":", charID) |
-- Make sure this server/character has data |
if( BeanCounterDB[server] and BeanCounterDB[server][name] ) then |
local auctionDB = BeanCounterDB[server][name] |
-- Loop through items we've succesfully bought out, or bid and won |
for itemid, rows in pairs(auctionDB["completedBids/Buyouts"]) do |
itemid = tonumber(itemid) |
for uniqueID, itemData in pairs(rows) do |
-- Annd loop through each transaction for this item |
for _, line in pairs(itemData) do |
local quantity, _, _, buyout, bid, buyer, arrivedAt = string.split(";", line) |
local time = getTime(arrivedAt, nil, 10) |
if( not auctionData[time] ) then |
auctionData[time] = {type = "day", time = time, temp = {}} |
end |
if( not auctionData[time].temp[itemid] ) then |
auctionData[time].temp[itemid] = { totalSold = 0, totalProfit = 0, totalMade = 0, totalDeposit = 0, totalFee = 0, totalSpent = 0, totalBought = 0 } |
end |
auctionData[time].temp[itemid].time = time |
auctionData[time].temp[itemid].itemid = itemid |
auctionData[time].temp[itemid].totalBought = auctionData[time].temp[itemid].totalBought + 1 |
-- If the buyout is 0 then we won it off of bid |
buyout = tonumber(buyout) |
if( buyout > 0 ) then |
auctionData[time].temp[itemid].totalSpent = auctionData[time].temp[itemid].totalSpent + buyout |
else |
auctionData[time].temp[itemid].totalSpent = auctionData[time].temp[itemid].totalSpent + bid |
end |
end |
end |
end |
-- Loop through items we've succesfully sold |
for itemid, rows in pairs(auctionDB["completedAuctions"]) do |
itemid = tonumber(itemid) |
for uniqueID, itemData in pairs(rows) do |
-- Loop through each item transaction |
for _, line in pairs(itemData) do |
local quantity, money, deposit, fee, buyout, bid, buyer, arrivedAt = string.split(";", line) |
local time = getTime(arrivedAt, nil, 10) |
if( not auctionData[time] ) then |
auctionData[time] = {type = "day", time = time, temp = {}} |
end |
if( not auctionData[time].temp[itemid] ) then |
auctionData[time].temp[itemid] = { totalSold = 0, totalProfit = 0, totalMade = 0, totalDeposit = 0, totalFee = 0, totalSpent = 0, totalBought = 0 } |
end |
auctionData[time].temp[itemid].time = time |
auctionData[time].temp[itemid].itemid = itemid |
auctionData[time].temp[itemid].totalSold = auctionData[time].temp[itemid].totalSold + 1 |
auctionData[time].temp[itemid].totalMade = auctionData[time].temp[itemid].totalMade + money |
auctionData[time].temp[itemid].totalDeposit = auctionData[time].temp[itemid].totalDeposit + deposit |
auctionData[time].temp[itemid].totalFee = auctionData[time].temp[itemid].totalFee + fee |
end |
end |
end |
end |
end |
-- Now make a summary based on month |
for time, row in pairs(auctionData) do |
if( row.type ~= "month" ) then |
local month = getTime(time, 1, 0) |
if( not auctionData[month] ) then |
auctionData[month] = {type = "month", time = month, temp = {}} |
end |
for itemid, data in pairs(row.temp) do |
if( not auctionData[month].temp[itemid] ) then |
auctionData[month].temp[itemid] = {} |
end |
-- Merge the items total stats, this months total stats, and this days total stats |
mergeDataTables(auctionData[month].temp[itemid], data) |
end |
end |
end |
-- Now we have to take our item tables, and turn them into indexed ones |
for time, row in pairs(auctionData) do |
row.totalProfit = 0 |
row.items = {} |
-- Add some final info, and add it into an indexed table |
for itemid, data in pairs(row.temp) do |
data.time = time |
data.itemid = itemid |
data.itemLink = select(2, GetItemInfo(itemid)) or itemid |
data.itemName = select(1, GetItemInfo(itemid)) or itemid |
data.totalProfit = data.totalMade - data.totalSpent |
row.totalProfit = row.totalProfit + data.totalProfit |
table.insert(row.items, data) |
mergeDataTables(auctionData[time], data) |
end |
-- Remove our temp table for gathering the data |
row.temp = nil |
end |
-- Add in the time formats so we can actually do our display things |
for time in pairs(auctionData) do |
table.insert(auctionDisplay, time) |
end |
table.sort(auctionDisplay, sortTime) |
end |
-- This is quickly hacked together, I'll clean it up later |
function AuctionStats:FormatNumber(number, decimal) |
-- Quick "rounding" |
if( decimal and math.floor(number) ~= number ) then |
number = string.format("%.1f", number) |
else |
number = math.floor(number + 0.5) |
end |
while( true ) do |
number, k = string.gsub(number, "^(-?%d+)(%d%d%d)", "%1,%2") |
if( k == 0 ) then break end |
end |
return number |
end |
function AuctionStats:UpdateBrowseGUI() |
local self = AuctionStats |
local totalRows = 0 |
for id, key in pairs(auctionDisplay) do |
local month = getTime(key, 1, 0) |
if( self.frame.monthKey == month or month == key ) then |
totalRows = totalRows + 1 |
end |
end |
FauxScrollFrame_Update(self.leftFrame.scroll, totalRows, MAX_DATE_ROWS - 1, 22) |
-- Hide everything to reset it |
for i=1, MAX_DATE_ROWS do |
self.dateRows[i].button:Hide() |
self.dateRows[i]:Hide() |
end |
-- List! |
local usedRows = 0 |
for id, key in pairs(auctionDisplay) do |
local month = getTime(key, 1, 0) |
if( ( self.frame.monthKey == month or month == key ) and id >= FauxScrollFrame_GetOffset(self.leftFrame.scroll) and usedRows < MAX_DATE_ROWS ) then |
usedRows = usedRows + 1 |
local color |
local data = auctionData[key] |
if( data.totalProfit < 0 ) then |
color = RED_FONT_COLOR_CODE |
elseif( data.totalProfit > 0 ) then |
color = GREEN_FONT_COLOR_CODE |
else |
color = "|cffffffff" |
end |
local row = self.dateRows[usedRows] |
row.profit:SetFormattedText("[%s%s%sg]", color, self:FormatNumber(data.totalProfit / 10000), FONT_COLOR_CODE_CLOSE) |
row.tooltip = string.format(L["Made: |cffffffff%s|rg\nSpent: |cffffffff%s|rg\nProfit: |cffffffff%s|rg"], self:FormatNumber(data.totalMade / 10000), self:FormatNumber(data.totalSpent / 10000), self:FormatNumber(data.totalProfit / 10000)) |
row.dateKey = data.time |
row.type = data.type |
row:Show() |
-- If it's a day, show day, # if it isn't show month, year |
if( data.type == "day" ) then |
row:SetText(date("%A, %d", data.time)) |
else |
row:SetText(date("%b %Y", data.time)) |
end |
-- Highlight |
if( self.frame.dateKey == data.time ) then |
row:SetTextColor(1, 0.81, 0) |
else |
row:SetTextColor(1, 1, 1) |
end |
-- Pushy |
if( data.type == "month" ) then |
row.button.monthKey = getTime(row.dateKey, 1, 0) |
if( self.frame.monthKey == data.time ) then |
row.button:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP") |
row.button:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN") |
row.button:SetHighlightTexture("Interface\\Buttons\\UI-MinusButton-Hilight", "ADD") |
else |
row.button:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP") |
row.button:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN") |
row.button:SetHighlightTexture("Interface\\Buttons\\UI-PlusButton-Hilight", "ADD") |
end |
end |
-- Reposition things based on the type, months have to be positioned over the button of course |
if( data.type == "month" ) then |
row:SetPoint("TOPLEFT", row.button, "TOPRIGHT", 0, 0) |
row.profit:SetPoint("TOPRIGHT", row, "TOPRIGHT", -15, -1) |
row.button:Show() |
else |
if( usedRows > 1 ) then |
if( self.dateRows[usedRows - 1].type == "month" ) then |
row:SetPoint("TOPLEFT", self.dateRows[usedRows - 1].button, "BOTTOMLEFT", 0, -1) |
else |
row:SetPoint("TOPLEFT", self.dateRows[usedRows - 1], "BOTTOMLEFT", 0, -1) |
end |
else |
row:SetPoint("TOPLEFT", self.leftFrame.scroll, "TOPLEFT", 4, 0) |
end |
row.profit:SetPoint("TOPRIGHT", row, "TOPRIGHT", -1, -1) |
row.button:Hide() |
end |
-- Adjust width if no scroll |
if( #(auctionDisplay) < MAX_DATE_ROWS ) then |
self.dateRows[usedRows]:SetWidth(168) |
else |
self.dateRows[usedRows]:SetWidth(149) |
end |
end |
end |
end |
local function sortBreakdownData(self) |
if( self.sortType ~= AuctionStats.frame.sortType ) then |
AuctionStats.frame.sortOrder = false |
AuctionStats.frame.sortType = self.sortType |
else |
AuctionStats.frame.sortOrder = not AuctionStats.frame.sortOrder |
end |
AuctionStats.frame.resortList = true |
AuctionStats:ViewBreakdown() |
end |
local function sortItemData(a, b) |
local sortBy = AuctionStats.frame.sortType |
if( AuctionStats.frame.sortOrder ) then |
return a[sortBy] < b[sortBy] |
else |
return a[sortBy] > b[sortBy] |
end |
end |
function AuctionStats:ViewBreakdown() |
local self = AuctionStats |
-- No data, or bad data |
if( not self.frame.dateKey or not auctionData[self.frame.dateKey] ) then |
for i=1, MAX_DATA_ROWS do |
for j=1, MAX_DATA_COLUMNS do |
self.rows[i][j]:Hide() |
end |
end |
FauxScrollFrame_Update(self.middleFrame.scroll, 0, MAX_DATA_ROWS - 1, 22) |
return |
end |
FauxScrollFrame_Update(self.middleFrame.scroll, #(auctionData[self.frame.dateKey].items), MAX_DATA_ROWS - 1, 22) |
if( self.frame.resortList ) then |
table.sort(auctionData[self.frame.dateKey].items, sortItemData) |
self.frame.resortList = nil |
end |
-- Hide everything to reset it |
for i=1, MAX_DATA_ROWS do |
for j=1, MAX_DATA_COLUMNS do |
self.rows[i][j]:Hide() |
end |
end |
-- List! |
local usedRows = 0 |
for id, data in pairs(auctionData[self.frame.dateKey].items) do |
if( id >= FauxScrollFrame_GetOffset(self.middleFrame.scroll) and usedRows < MAX_DATA_ROWS ) then |
usedRows = usedRows + 1 |
local row = self.rows[usedRows] |
row[1]:SetText(data.itemLink) |
row[1].tooltip = data.itemLink |
row[2]:SetFormattedText("%s|cffffffffg|r", self:FormatNumber(data.totalMade / 10000)) |
row[2]:SetTextColor(0, 1, 0) |
if( data.totalSold > 0 ) then |
row[2].tooltip = string.format(L["Auctions Completed: %d\nAverage Price: |cffffffff%s|rg"], data.totalSold, self:FormatNumber((data.totalMade / data.totalSold) / 10000, true)) |
else |
row[2].tooltip = string.format(L["Auctions Completed: %d\nAverage Price: |cffffffff%s|rg"], 0, 0) |
end |
row[3]:SetFormattedText("%s|cffffffffg|r", self:FormatNumber(data.totalSpent / 10000)) |
row[3]:SetTextColor(1, 0, 0) |
if( data.totalBought > 0 ) then |
row[3].tooltip = string.format(L["Auctions Won: %d\nAverage Price: |cffffffff%s|rg"], data.totalBought, self:FormatNumber((data.totalSpent / data.totalBought) / 10000, true)) |
else |
row[3].tooltip = string.format(L["Auctions Won: %d\nAverage Price: |cffffffff%s|rg"], 0, 0) |
end |
row[4]:SetFormattedText("%s|cffffffffg|r", self:FormatNumber(data.totalProfit / 10000)) |
if( data.totalProfit < 0 ) then |
row[4]:SetTextColor(1, 0, 0) |
elseif( data.totalProfit > 0 ) then |
row[4]:SetTextColor(0, 1, 0) |
else |
row[4]:SetTextColor(1, 1, 1) |
end |
for j=1, MAX_DATA_COLUMNS do |
self.rows[usedRows][j]:Show() |
end |
end |
end |
end |
-- GUI Creation |
function AuctionStats:CreateGUI() |
if( self.frame ) then |
return |
end |
self.frame = CreateFrame("Frame", "AuctionStatsGUI", UIParent) |
self.frame:SetWidth(550) |
self.frame:SetHeight(400) |
self.frame:SetMovable(true) |
self.frame:EnableMouse(true) |
self.frame:SetClampedToScreen(true) |
self.frame:SetBackdrop({ |
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", |
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", |
edgeSize = 26, |
insets = {left = 9, right = 9, top = 9, bottom = 9}, |
}) |
self.frame:SetScript("OnShow", function(self) |
AuctionStats:ParseData() |
-- Show the last days info if we have nothing selected |
if( not self.dateKey ) then |
self.dateKey = auctionDisplay[#(auctionDisplay)] |
self.monthKey = getTime(self.dateKey, 1, 0) |
AuctionStats:ViewBreakdown() |
end |
AuctionStats:UpdateBrowseGUI() |
end) |
self.frame.sortOrder = false |
self.frame.sortType = "totalProfit" |
self.frame.resortList = true |
self.frame:Hide() |
-- Make it act like a real frame |
self.frame:SetAttribute("UIPanelLayout-defined", true) |
self.frame:SetAttribute("UIPanelLayout-enabled", true) |
self.frame:SetAttribute("UIPanelLayout-area", "doublewide") |
self.frame:SetAttribute("UIPanelLayout-whileDead", true) |
table.insert(UISpecialFrames, "AuctionStatsGUI") |
-- Create the title/movy thing |
local texture = self.frame:CreateTexture(nil, "ARTWORK") |
texture:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header") |
texture:SetPoint("TOP", 0, 12) |
texture:SetWidth(250) |
texture:SetHeight(60) |
local title = CreateFrame("Button", nil, self.frame) |
title:SetPoint("TOP", 0, 4) |
title:SetText(L["Auction Stats"]) |
title:SetPushedTextOffset(0, 0) |
title:SetTextFontObject(GameFontNormal) |
title:SetHeight(20) |
title:SetWidth(200) |
title:RegisterForDrag("LeftButton") |
title:SetScript("OnDragStart", function(self) |
self.isMoving = true |
AuctionStats.frame:StartMoving() |
end) |
title:SetScript("OnDragStop", function(self) |
if( self.isMoving ) then |
self.isMoving = nil |
AuctionStats.frame:StopMovingOrSizing() |
end |
end) |
-- Close button, this needs more work not too happy with how it looks |
local button = CreateFrame("Button", nil, self.frame, "UIPanelCloseButton") |
button:SetHeight(27) |
button:SetWidth(27) |
button:SetPoint("TOPRIGHT", -1, -1) |
button:SetScript("OnClick", function() |
HideUIPanel(AuctionStats.frame) |
end) |
-- Container frame backdrop |
local backdrop = { |
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 } |
} |
-- Left 30%ish width panel |
self.leftFrame = CreateFrame("Frame", nil, self.frame) |
self.leftFrame:SetWidth(175) |
self.leftFrame:SetHeight(368) |
self.leftFrame:SetBackdrop(backdrop) |
self.leftFrame:SetBackdropColor(0, 0, 0, 0.65) |
self.leftFrame:SetBackdropBorderColor(0.75, 0.75, 0.75, 0.90) |
self.leftFrame:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 12, -20) |
-- Date scroll frame |
self.leftFrame.scroll = CreateFrame("ScrollFrame", "AuctionStatsGUIScrollLeft", self.frame, "FauxScrollFrameTemplate") |
self.leftFrame.scroll:SetPoint("TOPLEFT", self.leftFrame, "TOPLEFT", 0, -30) |
self.leftFrame.scroll:SetPoint("BOTTOMRIGHT", self.leftFrame, "BOTTOMRIGHT", -26, 3) |
self.leftFrame.scroll:SetScript("OnVerticalScroll", function() FauxScrollFrame_OnVerticalScroll(22, self.UpdateBrowseGUI) end) |
-- Create the date listing for the scroll frame |
self.dateRows = {} |
for i=1, MAX_DATE_ROWS do |
local row = CreateFrame("Button", nil, self.frame) |
row:SetWidth(149) |
row:SetHeight(14) |
row:SetTextFontObject(GameFontHighlightSmall) |
row:SetText("*") |
row:GetFontString():SetPoint("LEFT", row, "LEFT", 0, 0) |
row:SetScript("OnClick", showDisplayGUI) |
row:SetScript("OnEnter", showTooltip) |
row:SetScript("OnLeave", hideTooltip) |
row.profit = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") |
row.profit:SetText("*") |
row.profit:SetPoint("TOPRIGHT", row, "TOPRIGHT", -1, -1) |
row.button = CreateFrame("Button", nil, self.frame) |
row.button:SetScript("OnClick", monthToggle) |
row.button:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP") |
row.button:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN") |
row.button:SetHighlightTexture("Interface\\Buttons\\UI-PlusButton-Hilight", "ADD") |
row.button:SetHeight(14) |
row.button:SetWidth(14) |
if( i > 1 ) then |
row.button:SetPoint("TOPLEFT", self.dateRows[i - 1], "BOTTOMLEFT", 0, -1) |
else |
row.button:SetPoint("TOPLEFT", self.leftFrame.scroll, "TOPLEFT", 4, 0) |
end |
self.dateRows[i] = row |
end |
-- Check data |
self.gather = CreateFrame("Frame", "AuctionStatsGUIGatherDropdown", self.frame, "UIDropDownMenuTemplate") |
self.gather:SetPoint("TOPLEFT", self.leftFrame, "TOPLEFT", -13, -2) |
self.gather:SetScript("OnEnter", showTooltip) |
self.gather:SetScript("OnLeave", hideTooltip) |
self.gather.tooltip = L["Choose which characters data should be gathered from, from BeanCounter."], |
self.gather:EnableMouse(true) |
self.gather:SetScript("OnShow", function(self) |
UIDropDownMenu_Initialize(self, AuctionStats.InitGatherDropdown) |
UIDropDownMenu_SetWidth(150, self) |
end) |
-- Top frame, around 70% width panel |
self.topFrame = CreateFrame("Frame", nil, self.frame) |
self.topFrame:SetWidth(352) |
self.topFrame:SetHeight(40) |
self.topFrame:SetBackdrop(backdrop) |
self.topFrame:SetBackdropColor(0, 0, 0, 0.65) |
self.topFrame:SetBackdropBorderColor(0.75, 0.75, 0.75, 0.90) |
self.topFrame:SetPoint("TOPLEFT", self.leftFrame, "TOPRIGHT", 0, 0) |
-- Middle-ish frame, remaining space |
self.middleFrame = CreateFrame("Frame", nil, self.frame) |
self.middleFrame:SetWidth(352) |
self.middleFrame:SetHeight(328) |
self.middleFrame:SetBackdrop(backdrop) |
self.middleFrame:SetBackdropColor(0, 0, 0, 0.65) |
self.middleFrame:SetBackdropBorderColor(0.75, 0.75, 0.75, 0.90) |
self.middleFrame:SetPoint("TOPLEFT", self.topFrame, "BOTTOMLEFT", 0, 0) |
-- Date scroll frame |
self.middleFrame.scroll = CreateFrame("ScrollFrame", "AuctionStatsGUIScrollMiddle", self.frame, "FauxScrollFrameTemplate") |
self.middleFrame.scroll:SetPoint("TOPLEFT", self.middleFrame, "TOPLEFT", 0, -4) |
self.middleFrame.scroll:SetPoint("BOTTOMRIGHT", self.middleFrame, "BOTTOMRIGHT", -26, 3) |
self.middleFrame.scroll:SetScript("OnVerticalScroll", function() FauxScrollFrame_OnVerticalScroll(22, self.ViewBreakdown) end) |
-- Sort tabs |
self.sortButtons = {} |
for i=1, MAX_DATA_COLUMNS do |
local button = CreateFrame("Button", nil, self.frame) |
button:SetScript("OnClick", sortBreakdownData) |
button:SetHeight(20) |
button:SetTextFontObject(GameFontNormalSmall) |
button:Show() |
self.sortButtons[i] = button |
end |
self.sortButtons[1].sortType = "itemName" |
self.sortButtons[1]:SetText(L["Item"]) |
self.sortButtons[1]:SetWidth(self.sortButtons[1]:GetFontString():GetStringWidth() + 3) |
self.sortButtons[1]:SetPoint("TOPLEFT", self.middleFrame, "TOPLEFT", 4, -2) |
self.sortButtons[2].sortType = "totalMade" |
self.sortButtons[2]:SetText(L["Made"]) |
self.sortButtons[2]:SetWidth(self.sortButtons[2]:GetFontString():GetStringWidth() + 3) |
self.sortButtons[2]:SetPoint("TOPLEFT", self.sortButtons[1], "TOPRIGHT", 150, 0) |
self.sortButtons[3].sortType = "totalSpent" |
self.sortButtons[3]:SetText(L["Spent"]) |
self.sortButtons[3]:SetWidth(self.sortButtons[3]:GetFontString():GetStringWidth() + 3) |
self.sortButtons[3]:SetPoint("TOPLEFT", self.sortButtons[2], "TOPRIGHT", 15, 0) |
self.sortButtons[4].sortType = "totalProfit" |
self.sortButtons[4]:SetText(L["Profit"]) |
self.sortButtons[4]:SetWidth(self.sortButtons[4]:GetFontString():GetStringWidth() + 3) |
self.sortButtons[4]:SetPoint("TOPLEFT", self.sortButtons[3], "TOPRIGHT", 15, 0) |
-- Rows |
self.rows = {} |
for i=1, MAX_DATA_ROWS do |
self.rows[i] = {} |
for j=1, MAX_DATA_COLUMNS do |
local row = CreateFrame("Button", nil, self.frame) |
row:SetHeight(15) |
row:SetScript("OnEnter", showTooltip) |
row:SetScript("OnLeave", hideTooltip) |
row:SetTextFontObject(GameFontHighlightSmall) |
row:SetPushedTextOffset(0, 0) |
row:SetText("*") |
row.fs = row:GetFontString() |
row.fs:SetPoint("LEFT", row, "LEFT", 0, 0) |
row.fs:SetHeight(15) |
row.fs:SetJustifyH("LEFT") |
row:Hide() |
self.rows[i][j] = row |
if( j > 1 ) then |
row:SetWidth(50) |
else |
row:SetWidth(180) |
end |
if( i > 1 ) then |
row:SetPoint("TOPLEFT", self.rows[i - 1][j], "BOTTOMLEFT", 0, -1) |
else |
row:SetPoint("TOPLEFT", self.sortButtons[j], "BOTTOMLEFT", 1, 1) |
end |
end |
end |
-- Positioning |
self.frame:SetPoint("CENTER") |
end |
function AuctionStats:DropdownClicked() |
local server, name = string.split(":", this.value) |
UIDropDownMenu_SetText(string.format("%s - %s", name, server), AuctionStats.gather) |
AuctionStats.db.profile.gatherData[string.format("%s:%s", server,name)] = this.checked and true or nil |
end |
function AuctionStats:InitGatherDropdown() |
for server, charData in pairs(BeanCounterDB) do |
if( server ~= "settings" ) then |
for charName in pairs(charData) do |
local charID = string.format("%s:%s", server, charName) |
UIDropDownMenu_AddButton({ value = charID, text = string.format("%s - %s", charName, server), checked = AuctionStats.db.profile.gatherData[charID], keepShownOnClick = true, func = AuctionStats.DropdownClicked }) |
end |
end |
end |
UIDropDownMenu_SetText(string.format("%s - %s", UnitName("player"), GetRealmName()), AuctionStats.gather) |
end |
-- DEBUG |
--[[ |
local frame = CreateFrame("Frame") |
frame:RegisterEvent("PLAYER_ENTERING_WORLD") |
frame:SetScript("OnEvent", function() |
AuctionStats:CreateGUI() |
AuctionStats.frame:Show() |
frame:UnregisterAllEvents() |
end) |
]] |
AuctionStatLocals = { |
["Auction Stats"] = "Auction Stats", |
["Choose which characters data should be gathered from, from BeanCounter."] = "Choose which characters data should be gathered from, from BeanCounter.", |
["Made: |cffffffff%s|rg\nSpent: |cffffffff%s|rg\nProfit: |cffffffff%s|rg"] = "Made: |cffffffff%s|rg\nSpent: |cffffffff%s|rg\nProfit: |cffffffff%s|rg", |
["Auctions Completed: %d\nAverage Price: |cffffffff%s|rg"] = "Auctions Completed: %d\nAverage Price: |cffffffff%s|rg", |
["Auctions Won: %d\nAverage Price: |cffffffff%s|rg"] = "Auctions Won: %d\nAverage Price: |cffffffff%s|rg", |
["Item"] = "Item", |
["Made"] = "Made", |
["Spent"] = "Spent", |
["Profit"] = "Profit", |
["Avg Made"] = "Avg Made", |
["Avg Spent"] = "Avg Spent", |
} |
## Interface: 23000 |
## Title: Auction Stats |
## Notes: Plugin to BeanCounter that shows you more statistical information on your auctions |
## SavedVariables: AuctionStatsDB |
## Version: $Revision: 322 $ |
## Author: Mayen |
## RequiredDeps: BeanCounter |
libs\LibStub-1.0\LibStub-1.0.xml |
libs\CallbackHandler-1.0\CallbackHandler-1.0.xml |
libs\AceAddon-3.0\AceAddon-3.0.xml |
libs\AceEvent-3.0\AceEvent-3.0.xml |
localization.enUS.lua |
localization.deDE.lua |
localization.frFR.lua |
AuctionStats.lua |
if( GetLocale() ~= "frFR" ) then |
return |
end |
AuctionStatLocalss = setmetatable({ |
}, {__index = AuctionStatLocalss}) |
if( GetLocale() ~= "deDE" ) then |
return |
end |
AuctionStatLocalss = setmetatable({ |
}, {__index = AuctionStatLocalss}) |
<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> |
--[[ $Id: AceAddon-3.0.lua 60131 2008-02-03 13:03:56Z nevcairiel $ ]] |
local MAJOR, MINOR = "AceAddon-3.0", 1 |
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 |
local tinsert, tconcat = table.insert, table.concat |
local fmt = string.format |
local pairs, next, type = pairs, next, type |
--[[ |
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, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype |
-- used in the addon metatable |
local function addontostring( self ) return self.name end |
-- AceAddon:NewAddon( name, [lib, lib, lib, ...] ) |
-- name (string) - unique addon object name |
-- [lib] (string) - optional libs to embed in the addon object |
-- |
-- returns the addon object when succesful |
function AceAddon:NewAddon(name, ...) |
if type(name) ~= "string" then error(("Usage: NewAddon(name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end |
if self.addons[name] then error(("Usage: NewAddon(name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) end |
local addon = setmetatable( {name = name}, { __tostring = addontostring } ) |
self.addons[name] = addon |
addon.modules = {} |
addon.defaultModuleLibraries = {} |
Embed( addon ) -- embed NewModule, GetModule methods |
self:EmbedLibraries(addon, ...) |
-- add to queue of addons to be initialized upon ADDON_LOADED |
tinsert(self.initializequeue, addon) |
return addon |
end |
-- AceAddon:GetAddon( name, [silent]) |
-- name (string) - unique addon object name |
-- silent (boolean) - if true, addon is optional, silently return nil if its not found |
-- |
-- throws an error if the addon object can not be found (except silent is set) |
-- returns the addon object if found |
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 |
-- AceAddon:EmbedLibraries( addon, [lib, lib, lib, ...] ) |
-- addon (object) - addon to embed the libs in |
-- [lib] (string) - optional libs to embed |
function AceAddon:EmbedLibraries(addon, ...) |
for i=1,select("#", ... ) do |
local libname = select(i, ...) |
self:EmbedLibrary(addon, libname, false, 3) |
end |
end |
-- AceAddon:EmbedLibrary( addon, libname, silent, offset ) |
-- addon (object) - addon to embed the libs in |
-- libname (string) - lib to embed |
-- [silent] (boolean) - optional, marks an embed to fail silently if the library doesn't exist. |
-- [offset] (number) - will push the error messages back to said offset defaults to 2 |
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 |
-- addon:GetModule( name, [silent]) |
-- name (string) - unique module object name |
-- silent (boolean) - if true, module is optional, silently return nil if its not found |
-- |
-- throws an error if the addon object can not be found (except silent is set) |
-- returns the module object if found |
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 |
-- addon:NewModule( name, [prototype, [lib, lib, lib, ...] ) |
-- name (string) - unique module object name for this addon |
-- prototype (object) - object to derive this module from, methods and values from this table will be mixed into the module, if a string is passed a lib is assumed |
-- [lib] (string) - optional libs to embed in the addon object |
-- |
-- returns the addon object when succesful |
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) |
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 |
return module |
end |
--addon:Enable() |
-- Enables the Addon if possible, return true or false depending on success |
function Enable(self) |
self:SetEnabledState(true) |
return AceAddon:EnableAddon(self) |
end |
--addon:Disable() |
-- Disables the Addon if possible, return true or false depending on success |
function Disable(self) |
self:SetEnabledState(false) |
return AceAddon:DisableAddon(self) |
end |
-- addon:EnableModule( name ) |
-- name (string) - unique module object name |
-- |
-- Enables the Module if possible, return true or false depending on success |
function EnableModule(self, name) |
local module = self:GetModule( name ) |
return module:Enable() |
end |
-- addon:DisableModule( name ) |
-- name (string) - unique module object name |
-- |
-- Disables the Module if possible, return true or false depending on success |
function DisableModule(self, name) |
local module = self:GetModule( name ) |
return module:Disable() |
end |
-- addon:SetDefaultModuleLibraries( [lib, lib, lib, ...] ) |
-- [lib] (string) - libs to embed in every module |
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 |
-- addon:SetDefaultModuleState( state ) |
-- state (boolean) - default state for new modules (enabled=true, disabled=false) |
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 |
-- addon:SetDefaultModulePrototype( prototype ) |
-- prototype (string or table) - the default prototype to use if none is specified on module creation |
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 |
-- addon:SetEnabledState ( state ) |
-- state ( boolean ) - set the state of an addon or module (enabled=true, disabled=false) |
-- |
-- should only be called before any Enabling actually happend, aka in OnInitialize |
function SetEnabledState(self, state) |
self.enabledState = state |
end |
local function IterateModules(self) return pairs(self.modules) end |
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) 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, |
} |
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) |
for k, v in pairs(mixins) do |
target[k] = v |
end |
for k, v in pairs(pmixins) do |
target[k] = target[k] or v |
end |
end |
-- AceAddon:IntializeAddon( addon ) |
-- addon (object) - addon to intialize |
-- |
-- calls OnInitialize on the addon object if available |
-- calls OnEmbedInitialize on embedded libs in the addon object if available |
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 |
-- AceAddon:EnableAddon( addon ) |
-- addon (object) - addon to enable |
-- |
-- calls OnEnable on the addon object if available |
-- calls OnEmbedEnable on embedded libs in the addon object if available |
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 |
-- TODO: handle 'first'? Or let addons do it on their own? |
safecall(addon.OnEnable, addon) |
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 |
self.statuses[addon.name] = addon.enabledState |
-- enable possible modules. |
for name, module in pairs(addon.modules) do |
self:EnableAddon(module) |
end |
return true |
end |
-- AceAddon:DisableAddon( addon ) |
-- addon (object|string) - addon to disable |
-- |
-- calls OnDisable on the addon object if available |
-- calls OnEmbedDisable on embedded libs in the addon object if available |
function AceAddon:DisableAddon(addon) |
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end |
if not self.statuses[addon.name] then return false end |
safecall( addon.OnDisable, addon ) |
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 |
self.statuses[addon.name] = addon.enabledState |
-- disable possible modules. |
for name, module in pairs(addon.modules) do |
self:DisableAddon(module) |
end |
return true |
end |
--The next few funcs are just because no one should be reaching into the internal registries |
--Thoughts? |
function AceAddon:IterateAddons() return pairs(self.addons) end |
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end |
function AceAddon:IterateAddonStatus() return pairs(self.statuses) end |
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end |
-- Event Handling |
local function onEvent(this, event, arg1) |
if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then |
for i = 1, #AceAddon.initializequeue do |
local addon = AceAddon.initializequeue[i] |
if event == "ADDON_LOADED" then addon.baseName = arg1 end |
AceAddon.initializequeue[i] = nil |
AceAddon:InitializeAddon(addon) |
tinsert(AceAddon.enablequeue, addon) |
end |
if IsLoggedIn() then |
for i = 1, #AceAddon.enablequeue do |
local addon = AceAddon.enablequeue[i] |
AceAddon.enablequeue[i] = nil |
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) |
end |
<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> |
--[[ $Id: AceDB-3.0.lua 69511 2008-04-13 10:10:53Z nevcairiel $ ]] |
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 7 |
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) |
if not AceDB then return end -- No upgrade needed |
local type = type |
local pairs, next = pairs, next |
local rawget, rawset = rawget, rawset |
local setmetatable = setmetatable |
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) |
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 then |
removeDefaults(value, v) |
-- if the table is empty afterwards, remove it |
if not next(value) 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 not next(db[k]) 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 |
-- remove all metatables from the db |
setmetatable(db, nil) |
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 |
-- Actual database initialization function |
local function initdb(sv, defaults, defaultProfile, olddb, parent) |
-- Generate the database keys for each section |
-- Make a container for profile keys |
if not sv.profileKeys then sv.profileKeys = {} end |
-- Try to get the profile selected from the char db |
local profileKey = sv.profileKeys[charKey] or defaultProfile or charKey |
sv.profileKeys[charKey] = profileKey |
-- 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, |
["profile"] = profileKey, |
["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 |
local function logoutHandler(frame, event) |
if event == "PLAYER_LOGOUT" then |
for db in pairs(AceDB.db_registry) do |
db.callbacks:Fire("OnDatabaseShutdown", db) |
for section, key in pairs(db.keys) do |
if db.defaults and db.defaults[section] and rawget(db, section) then |
removeDefaults(db[section], db.defaults[section]) |
end |
end |
end |
end |
end |
AceDB.frame:RegisterEvent("PLAYER_LOGOUT") |
AceDB.frame:SetScript("OnEvent", logoutHandler) |
--[[------------------------------------------------------------------------- |
AceDB Object Method Definitions |
---------------------------------------------------------------------------]] |
-- DBObject:RegisterDefaults(defaults) |
-- defaults (table) - A table of defaults for this database |
-- |
-- Sets the defaults table for the given database object by clearing any |
-- that are currently set, and then setting the new defaults. |
function DBObjectLib:RegisterDefaults(defaults) |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 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 |
-- DBObject:SetProfile(name) |
-- name (string) - The name of the profile to set as the current profile |
-- |
-- Changes the profile of the database and all of it's namespaces to the |
-- supplied named profile |
function DBObjectLib:SetProfile(name) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 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 |
if oldProfile and defaults then |
-- Remove the defaults from the old profile |
removeDefaults(oldProfile, defaults) |
end |
self.profile = nil |
self.keys["profile"] = name |
self.sv.profileKeys[charKey] = name |
-- 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 |
-- DBObject:GetProfiles(tbl) |
-- tbl (table) - A table to store the profile names in (optional) |
-- |
-- 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. |
function DBObjectLib:GetProfiles(tbl) |
if tbl and type(tbl) ~= "table" then |
error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 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 |
-- DBObject:GetCurrentProfile() |
-- |
-- Returns the current profile name used by the database |
function DBObjectLib:GetCurrentProfile() |
return self.keys.profile |
end |
-- DBObject:DeleteProfile(name) |
-- name (string) - The name of the profile to be deleted |
-- |
-- Deletes a named profile. This profile must not be the active profile. |
function DBObjectLib:DeleteProfile(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) |
end |
if self.keys.profile == name then |
error("Cannot delete the active profile in an AceDBObject.", 2) |
end |
if not rawget(self.sv.profiles, name) and not silent then |
error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) |
end |
self.sv.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 |
-- Callback: OnProfileDeleted, database, profileKey |
self.callbacks:Fire("OnProfileDeleted", self, name) |
end |
-- DBObject:CopyProfile(name) |
-- name (string) - The name of the profile to be copied into the current profile |
-- |
-- Copies a named profile into the current profile, overwriting any conflicting |
-- settings. |
function DBObjectLib:CopyProfile(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) |
end |
if name == self.keys.profile then |
error("Cannot have the same source and destination profiles.", 2) |
end |
if not rawget(self.sv.profiles, name) and not silent then |
error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) |
end |
-- Reset the profile before copying |
DBObjectLib.ResetProfile(self) |
local profile = self.profile |
local source = self.sv.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 |
-- DBObject:ResetProfile() |
-- noChildren (boolean) - if set to true, the reset will not be populated to the child namespaces of this DB object |
-- |
-- Resets the current profile |
function DBObjectLib:ResetProfile(noChildren) |
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) |
end |
end |
-- Callback: OnProfileReset, database |
self.callbacks:Fire("OnProfileReset", self) |
end |
-- DBObject:ResetDB(defaultProfile) |
-- defaultProfile (string) - The profile name to use as the default |
-- |
-- Resets the entire database, using the string defaultProfile as the default |
-- profile. |
function DBObjectLib:ResetDB(defaultProfile) |
if defaultProfile and type(defaultProfile) ~= "string" then |
error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) |
end |
local sv = self.sv |
for k,v in pairs(sv) do |
sv[k] = nil |
end |
local parent = self.parent |
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 |
-- DBObject:RegisterNamespace(name [, defaults]) |
-- name (string) - The name of the new namespace |
-- defaults (table) - A table of values to use as defaults |
-- |
-- 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 |
function DBObjectLib:RegisterNamespace(name, defaults) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) |
end |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) |
end |
if self.children and self.children[name] then |
error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 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 |
--[[------------------------------------------------------------------------- |
AceDB Exposed Methods |
---------------------------------------------------------------------------]] |
-- AceDB:New(name, defaults, defaultProfile) |
-- name (table or string) - The name of variable, or table to use for the database |
-- defaults (table) - A table of database defaults |
-- defaultProfile (string) - The name of the default profile |
-- |
-- Creates a new database object that can be used to handle database settings |
-- and profiles. |
function AceDB:New(tbl, defaults, defaultProfile) |
if type(tbl) == "string" then |
local name = tbl |
tbl = getglobal(name) |
if not tbl then |
tbl = {} |
setglobal(name, tbl) |
end |
end |
if type(tbl) ~= "table" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) |
end |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) |
end |
if defaultProfile and type(defaultProfile) ~= "string" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string expected.", 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 |
end |
end |
<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="LibStub-1.0.lua"/> |
</Ui> |
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info |
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke |
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! |
local LibStub = _G[LIBSTUB_MAJOR] |
if not LibStub or LibStub.minor < LIBSTUB_MINOR then |
LibStub = LibStub or {libs = {}, minors = {} } |
_G[LIBSTUB_MAJOR] = LibStub |
LibStub.minor = LIBSTUB_MINOR |
function LibStub:NewLibrary(major, minor) |
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") |
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") |
local oldminor = self.minors[major] |
if oldminor and oldminor >= minor then return nil end |
self.minors[major], self.libs[major] = minor, self.libs[major] or {} |
return self.libs[major], oldminor |
end |
function LibStub:GetLibrary(major, silent) |
if not self.libs[major] and not silent then |
error(("Cannot find a library instance of %q."):format(tostring(major)), 2) |
end |
return self.libs[major], self.minors[major] |
end |
function LibStub:IterateLibraries() return pairs(self.libs) end |
setmetatable(LibStub, { __call = LibStub.GetLibrary }) |
end |
<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="CallbackHandler-1.0.lua"/> |
</Ui> |
--[[ $Id: CallbackHandler-1.0.lua 60131 2008-02-03 13:03:56Z nevcairiel $ ]] |
local MAJOR, MINOR = "CallbackHandler-1.0", 2 |
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) |
if not CallbackHandler then return end -- No upgrade needed |
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} |
local type = type |
local pcall = pcall |
local pairs = pairs |
local assert = assert |
local concat = table.concat |
local loadstring = loadstring |
local next = next |
local select = select |
local type = type |
local xpcall = xpcall |
local function errorhandler(err) |
return geterrorhandler()(err) |
end |
local function CreateDispatcher(argCount) |
local code = [[ |
local next, xpcall, eh = ... |
local method, ARGS |
local function call() method(ARGS) end |
local function dispatch(handlers, ...) |
local index |
index, method = next(handlers) |
if not method then return end |
local OLD_ARGS = ARGS |
ARGS = ... |
repeat |
xpcall(call, eh) |
index, method = next(handlers, index) |
until not method |
ARGS = OLD_ARGS |
end |
return dispatch |
]] |
local ARGS, OLD_ARGS = {}, {} |
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end |
code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", ")) |
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) |
end |
local Dispatchers = setmetatable({}, {__index=function(self, argCount) |
local dispatcher = CreateDispatcher(argCount) |
rawset(self, argCount, dispatcher) |
return dispatcher |
end}) |
-------------------------------------------------------------------------- |
-- CallbackHandler:New |
-- |
-- target - target object to embed public APIs in |
-- RegisterName - name of the callback registration API, default "RegisterCallback" |
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" |
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. |
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) |
-- TODO: Remove this after beta has gone out |
assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") |
RegisterName = RegisterName or "RegisterCallback" |
UnregisterName = UnregisterName or "UnregisterCallback" |
if UnregisterAllName==nil then -- false is used to indicate "don't want this method" |
UnregisterAllName = "UnregisterAllCallbacks" |
end |
-- we declare all objects and exported APIs inside this closure to quickly gain access |
-- to e.g. function names, the "target" parameter, etc |
-- Create the registry object |
local events = setmetatable({}, meta) |
local registry = { recurse=0, events=events } |
-- registry:Fire() - fires the given event/message into the registry |
function registry:Fire(eventname, ...) |
local oldrecurse = registry.recurse |
registry.recurse = oldrecurse + 1 |
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) |
registry.recurse = oldrecurse |
if registry.insertQueue and oldrecurse==0 then |
-- Something in one of our callbacks wanted to register more callbacks; they got queued |
for eventname,callbacks in pairs(registry.insertQueue) do |
for self,func in pairs(callbacks) do |
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. |
events[eventname][self] = func |
-- fire OnUsed callback? |
if first and registry.OnUsed then |
registry.OnUsed(registry, target, eventname) |
end |
end |
end |
registry.insertQueue = nil |
end |
end |
-- Registration of a callback, handles: |
-- self["method"], leads to self["method"](self, ...) |
-- self with function ref, leads to functionref(...) |
-- "addonId" (instead of self) with function ref, leads to functionref(...) |
-- all with an optional arg, which, if present, gets passed as first argument (after self if present) |
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) |
if type(eventname) ~= "string" then |
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) |
end |
method = method or eventname |
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. |
if type(method) ~= "string" and type(method) ~= "function" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) |
end |
local regfunc |
if type(method) == "string" then |
-- self["method"] calling style |
if type(self) ~= "table" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) |
elseif self==target then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) |
elseif type(self[method]) ~= "function" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) self[method](self,arg,...) end |
else |
regfunc = function(...) self[method](self,...) end |
end |
else |
-- function ref with self=object or self="addonId" |
if type(self)~="table" and type(self)~="string" then |
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) method(arg,...) end |
else |
regfunc = method |
end |
end |
if events[eventname][self] or registry.recurse<1 then |
-- if registry.recurse<1 then |
-- we're overwriting an existing entry, or not currently recursing. just set it. |
events[eventname][self] = regfunc |
-- fire OnUsed callback? |
if registry.OnUsed and first then |
registry.OnUsed(registry, target, eventname) |
end |
else |
-- we're currently processing a callback in this registry, so delay the registration of this new entry! |
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency |
registry.insertQueue = registry.insertQueue or setmetatable({},meta) |
registry.insertQueue[eventname][self] = regfunc |
end |
end |
-- Unregister a callback |
target[UnregisterName] = function(self, eventname) |
if not self or self==target then |
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) |
end |
if type(eventname) ~= "string" then |
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) |
end |
if rawget(events, eventname) and events[eventname][self] then |
events[eventname][self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(events[eventname]) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then |
registry.insertQueue[eventname][self] = nil |
end |
end |
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds |
if UnregisterAllName then |
target[UnregisterAllName] = function(...) |
if select("#",...)<1 then |
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) |
end |
if select("#",...)==1 and ...==target then |
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) |
end |
for i=1,select("#",...) do |
local self = select(i,...) |
if registry.insertQueue then |
for eventname, callbacks in pairs(registry.insertQueue) do |
if callbacks[self] then |
callbacks[self] = nil |
end |
end |
end |
for eventname, callbacks in pairs(events) do |
if callbacks[self] then |
callbacks[self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(callbacks) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
end |
end |
end |
end |
return registry |
end |
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it |
-- try to upgrade old implicit embeds since the system is selfcontained and |
-- relies on closures to work. |
<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> |
--[[ $Id: AceEvent-3.0.lua 60131 2008-02-03 13:03:56Z nevcairiel $ ]] |
local MAJOR, MINOR = "AceEvent-3.0", 3 |
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceEvent then return end |
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") |
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", |
} |
-- AceEvent:Embed( target ) |
-- target (object) - target object to embed AceEvent in |
-- |
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:.. |
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 |