diff --git a/VitalWatch.lua b/VitalWatch.lua new file mode 100644 index 0000000..6932100 --- /dev/null +++ b/VitalWatch.lua @@ -0,0 +1,1249 @@ +--[[ +-- Name: VitalWatch +-- Author: Thrae of Maelstrom (aka "Matthew Carras") +-- Release Date: 8-25-06 +-- +-- Original concept by Sean Kennedy (HealthWatch), then modified by Nikolas Davis (VitalWatch). +-- This is a complete recode of the original VitalWatch, with a lot of new features. +-- +-- Thanks to #wowace and #wowi-lounge on Freenode as always for +-- optimization assistance. +--]] + +local _G = getfenv(0) +local strfind = string.find + +local UnitExists, UnitIsUnit, UnitCanAttack, UnitIsPlayer, UnitName, UnitInRaid, UnitInParty, UnitLevel = _G.UnitExists, _G.UnitIsUnit, _G.UnitCanAttack, _G.UnitIsPlayer, _G.UnitName, _G.UnitInRaid, _G.UnitInParty, _G.UnitLevel + +local GetTime = _G.GetTime + +VitalWatch = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceDB-2.0", "FuBarPlugin-2.0") +local VitalWatch = _G.VitalWatch +local VitalWatchLocale = _G.VitalWatchLocale +local tablet = AceLibrary("Tablet-2.0") +local roster + +-- stuff for FuBar / Tablet +--VitalWatch.name = "VitalWatch" -- should be auto provided if using AceAddon-2.0 +VitalWatch.title = VitalWatchLocale.LogTitle +VitalWatch.defaultPosition = "LEFT" +VitalWatch.defaultMinimapPosition = 150 +VitalWatch.cannotDetachTooltip = false +VitalWatch.hideWithoutStandby = true +VitalWatch.clickableTooltip = true -- XXX now do what? +VitalWatch.independentProfile = true +--VitalWatch.tooltipHiddenWhenEmpty = true +VitalWatch.showTitleWhenDetached = true +VitalWatch.hasIcon = "Interface\\Icons\\Spell_ChargePositive" + +local db, tmp, _ + +-- These are frames from the default UI +local LowHealthFrame = _G.LowHealthFrame +local OutOfControlFrame = _G.OutOfControlFrame + +local LastPercentageHealth +local LastPercentageMana +local MessageLog +local NumMessageLog = 0 + +local MessageFrameHoldTime = 3 -- How long the message is held on the frame. +local MessageFrameMessageAlpha = 1 + +local NextQueuedMessage, NextQueuedMessageChan, NextQueuedMessagePriority, NextQueuedEmote, NextQueuedDoEmote +local DEFAULT_MsgRate = 0.2 +local DEFAULT_AggroWatchRate = 5 +local DEFAULT_AggroWatchOtherRate = 8 + +-- hopefully this won't need to be localized +local SoundPath = "Interface\\AddOns\\VitalWatch\\sounds\\" +local HeartbeatSound = SoundPath .. "Heartbeat.wav" + +local DEFAULT_ThresholdCritHealth = 0.2 +local DEFAULT_ThresholdLowHealth = 0.4 +local DEFAULT_ThresholdCritMana = 0.2 +local DEFAULT_ThresholdLowMana = 0.4 +local DEFAULT_SoundCritHealth = SoundPath .. "phasers3.wav" +local DEFAULT_SoundLowHealth = SoundPath .. "blip5.wav" + +local DEFAULT_SoundCritHealthByClass = { + ["DRUID"] = SoundPath .. "phasers3druid.wav", + ["HUNTER"] = SoundPath .. "phasers3hunter.wav", + ["MAGE"] = SoundPath .. "phasers3mage.wav", + ["PALADIN"] = SoundPath .. "phasers3paladin.wav", + ["PRIEST"] = SoundPath .. "phasers3priest.wav", + ["ROGUE"] = SoundPath .. "phasers3rogue.wav", + ["WARLOCK"] = SoundPath .. "phasers3warlock.wav", + ["WARRIOR"] = SoundPath .. "phasers3warrior.wav" +} +local DEFAULT_SoundLowHealthByClass = { + ["DRUID"] = SoundPath .. "blip5druid.wav", + ["HUNTER"] = SoundPath .. "blip5hunter.wav", + ["MAGE"] = SoundPath .. "blip5mage.wav", + ["PALADIN"] = SoundPath .. "blip5paladin.wav", + ["PRIEST"] = SoundPath .. "blip5priest.wav", + ["ROGUE"] = SoundPath .. "blip5rogue.wav", + ["WARLOCK"] = SoundPath .. "blip5warlock.wav", + ["WARRIOR"] = SoundPath .. "blip5warrior.wav" +} +local DEFAULT_SoundCritManaByClass = { + ["DRUID"] = SoundPath .. "shrinkdruid.wav", + ["HUNTER"] = SoundPath .. "shrinkhunter.wav", + ["MAGE"] = SoundPath .. "shrinkmage.wav", + ["PALADIN"] = SoundPath .. "shrinkpaladin.wav", + ["PRIEST"] = SoundPath .. "shrinkpriest.wav", + ["WARLOCK"] = SoundPath .. "shrinkwarlock.wav" +} + +local DEFAULT_SoundAggroByClass = { + ["DRUID"] = SoundPath .. "blaredruid.wav", + ["HUNTER"] = SoundPath .. "blarehunter.wav", + ["PALADIN"] = SoundPath .. "blarepaladin.wav", + ["PRIEST"] = SoundPath .. "blarepriest.wav", + ["ROGUE"] = SoundPath .. "blarerogue.wav", + ["WARLOCK"] = SoundPath .. "blarewarlock.wav", + ["WARRIOR"] = SoundPath .. "blarewarrior.wav" +} + +local ClassColors = {} +for k,v in pairs(RAID_CLASS_COLORS) do + ClassColors[k] = string.format("%2x%2x%2x", v.r*255, v.g*255, v.b*255) +end + +-- Print out a message, probably to ChatFrame1 +function VitalWatch:Msg(msg) + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFCC00VitalWatch:|r " .. msg ) +end + +-- Enable a LoadOnDemand addon and optionally run one of its functions. +-- If the addon is already loaded, then just run the function or do +-- nothing. +function VitalWatch:LoDRun(addon,sfunc,arg1,arg2,arg3,arg4,arg5,arg6) + if not self[sfunc] then + local loaded, reason = LoadAddOn(addon) + if loaded then + if self[sfunc] and type( self[sfunc] ) == "function" then + self[sfunc](self,arg1,arg2,arg3,arg4,arg5,arg6) + end + else + self:Msg( addon .. " Addon LoadOnDemand Error - " .. reason ) + return reason + end + elseif type( self[sfunc] ) == "function" then + self[sfunc](self,arg1,arg2,arg3,arg4,arg5,arg6) + end +end + +local MessageFrame, MessageFrameHealth_AddMessage, MessageFrameMana_AddMessage, MessageFrameAggro_AddMessage +local function CreateMessageFrame() + if not MessageFrame then + MessageFrame = CreateFrame("MessageFrame", "VitalWatchMessageFrame", UIParent ) + MessageFrame:SetWidth(512) + MessageFrame:SetHeight(60) + MessageFrame:SetPoint("CENTER", 0, 250) + MessageFrame:SetInsertMode("TOP") + MessageFrame:SetToplevel(true) + MessageFrame:SetFontObject(NumberFontNormalHuge) + MessageFrame:SetJustifyH("CENTER") + MessageFrame:Show() + end +end + +local function StopFrameFlash(frame) + UIFrameFlashRemoveFrame(frame) + UIFrameFadeRemoveFrame(frame) + if frame:IsVisible() then frame:Hide() end +end + +-- Thanks to TNE_LowHealthWarning for part of this. +local function AdjustFlashRate(frame, rate) + if rate and rate < 1 then + if UIFrameIsFlashing(frame) then + frame.flashDuration = frame.flashDuration + rate + 1 + if rate < 1 then + frame.fadeInTime = rate * 0.15 + frame.fadeOutTime = rate * 0.85 + frame.flashInHoldTime = 0 + elseif rate > 0 then + frame.fadeInTime = 0.2 + frame.fadeOutTime = 0.8 + frame.flashInHoldTime = rate - 1 + else + StopFrameFlash(frame) + end + elseif rate > 0 then + UIFrameFlash(frame, 0.2, 0.8, 10, nil, rate -1, 0) + else + StopFrameFlash(frame) + end + end +end + +-- I want to get rid of this +local function PlayHeartbeatSound() + PlaySoundFile( HeartbeatSound ) +end + +-- Alternative message frames. I used BigWigs as a template. +local colourtable +local function SCT4_AddMessage(frame, text, r, g, b, alpha) + if not colourtable then colourtable = {} end + colourtable.r, colourtable.g, colourtable.b = r, g, b + SCT_Display_Message( text, colourtable ) +end + +local function SCT_AddMessage(frame, text, r, g, b, alpha) + SCT_MSG_FRAME:AddMessage(text, r, g, b, alpha) +end + +local function MikSBT_AddMessage(frame, text, r, g, b) + MikSBT.DisplayMessage(text, MikSBT.DISPLAYTYPE_NOTIFICATION, false, r * 255, g * 255, b * 255) +end + +local function BigWigs_AddMessage(frame, text, r, g, b, alpha) + if not colourtable then colourtable = {} end + colourtable.r, colourtable.g, colourtable.b = r,g,b + VitalWatch:TriggerEvent("BigWigs_Message", text, colourtable, true, nil) +end + +local function Blizzard_FCT_AddMessage(frame, text, r, g, b, alpha) + CombatText_AddMessage(text, COMBAT_TEXT_SCROLL_FUNCTION, r, g, b, "sticky", nil) +end + +------------------------------------------------------------------------- +-- WoW Event Processing + +local playerInBG +function VitalWatch:PLAYER_ENTERING_WORLD() + local isIn,typeOf=IsInInstance() + playerInBG = isIn and (typeOf == "pvp") +end + +function VitalWatch:ProcessUnitHealthOther(unit, name, class, petTag) + local percentage = UnitHealth(unit) / UnitHealthMax(unit) + if percentage < (db["ThresholdCritHealth"] or DEFAULT_ThresholdCritHealth) and + (not LastPercentageHealth[unit] or + LastPercentageHealth[unit] >= (db["ThresholdCritHealth"] or DEFAULT_ThresholdCritHealth)) then + if class and ClassColors[class] then + self:MessageLogUpdate("|cFF" .. ClassColors[class] .. name .. "|r" .. + VitalWatchLocale.Floating_Message_CritHealth, + db["ColourCritHealthOtherR"] or 1, + db["ColourCritHealthOtherG"] or 0, + db["ColourCritHealthOtherB"] or 0) + else + self:MessageLogUpdate(name .. VitalWatchLocale.Floating_Message_CritHealth .. (petTag or ""), + db["ColourCritHealthOtherR"] or 1, + db["ColourCritHealthOtherG"] or 0, + db["ColourCritHealthOtherB"] or 0) + end + if db["MessageFrameHealth"] ~= 5 then + MessageFrameHealth_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + + tmp = db["SoundCritHealthByClass"] + tmp = tmp ~= nil and tmp[(class or "pet")] + if tmp then + PlaySoundFile( DEFAULT_SoundCritHealthByClass[class] ) + else + tmp = db["SoundCritHealthOther"] + if tmp ~= "DISABLED" then + PlaySoundFile( (tmp ~= nil and (SoundPath .. tmp .. ".wav") ) or DEFAULT_SoundCritHealth) + end + end + + tmp = db["MsgChanOther"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and + (not NextQueuedMessagePriority or (NextQueuedMessagePriority < 3 and + not petTag)) and db["MsgCritHealth"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + (tmp == "PARTY" and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ------- + NextQueuedMessage = "<< " .. + name .. + db["MsgOtherCritHealth"] .. + (petTag or (" (" .. UnitClass(unit) .. ") ")) .. + " >>" + NextQueuedMessageChan = db["MsgChanOther"] + NextQueuedMessagePriority = (petTag and 1) or 2 + -------- + end + elseif percentage < (db["ThresholdLowHealth"] or DEFAULT_ThresholdLowHealth) and + (not LastPercentageHealth[unit] or + LastPercentageHealth[unit] >= (db["ThresholdLowHealth"] or DEFAULT_ThresholdLowHealth)) then + + if class and ClassColors[class] then + self:MessageLogUpdate("|cFF" .. ClassColors[class] .. name .. "|r" .. + VitalWatchLocale.Floating_Message_LowHealth, + db["ColourLowHealthOtherR"] or 1, + db["ColourLowHealthOtherG"] or 0, + db["ColourLowHealthOtherB"] or 0) + else + self:MessageLogUpdate(name .. VitalWatchLocale.Floating_Message_LowHealth .. (petTag or ""), + db["ColourLowHealthOtherR"] or 1, + db["ColourLowHealthOtherG"] or 0, + db["ColourLowHealthOtherB"] or 0) + end + if db["MessageFrameHealth"] ~= 5 then + MessageFrameHealth_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + + tmp = db["SoundLowHealthByClass"] + tmp = tmp ~= nil and tmp[(class or "pet")] + if tmp then + PlaySoundFile( DEFAULT_SoundLowHealthByClass[class] ) + else + tmp = db["SoundLowHealthOther"] + if tmp ~= "DISABLED" then + PlaySoundFile( (tmp ~= nil and (SoundPath .. tmp .. ".wav") ) or DEFAULT_SoundLowHealth) + end + end + + tmp = db["MsgChanOther"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and + (not NextQueuedMessagePriority or (NextQueuedMessagePriority < 2 and + not petTag)) and db["MsgCritHealth"] and not playerInBG and + db["MsgOtherLowHealth"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + (tmp == "PARTY" and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ------- + NextQueuedMessage = "<< " .. + name .. + db["MsgOtherLowHealth"] .. + (petTag or (" (" .. UnitClass(unit) .. ")")) .. + " >>" + NextQueuedMessageChan = db["MsgChanOther"] + NextQueuedMessagePriority = (not petTag and 1) or nil + -------- + end + end + LastPercentageHealth[unit] = percentage +end + +function VitalWatch:UNIT_HEALTH(unit) + if unit and unit ~= "target" and unit ~= "focus" and UnitAffectingCombat(unit) and + UnitHealth(unit) > 0 and not UnitIsDeadOrGhost(unit) then + if unit == "player" or UnitIsUnit(unit, "player") then + local flashRestart + local percentage = UnitHealth(unit) / UnitHealthMax(unit) + if percentage < (db["ThresholdCritHealth"] or DEFAULT_ThresholdCritHealth) and + (not LastPercentageHealth[unit] or + LastPercentageHealth[unit] >= (db["ThresholdCritHealth"] or DEFAULT_ThresholdCritHealth)) then + if db["FlashFrameHealth"] then + AdjustFlashRate(LowHealthFrame, (2.0 * percentage) + 0.5) + flashRestart = true + end + if db["Heartbeat"] then + self:ScheduleRepeatingEvent( "Heartbeat", + PlayHeartbeatSound, + (2.0 * percentage) + 0.5 ) + end + self:MessageLogUpdate(VitalWatchLocale.Floating_Message_Self_CritHealth, + db["ColourCritHealthR"] or 1, + db["ColourCritHealthG"] or 0, + db["ColourCritHealthB"] or 0) + if db["MessageFrameHealth"] ~= 5 then + MessageFrameHealth_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + local skipsound + tmp = db["EmoteCritHealth"] + if tmp and not playerInBG then + local doEmote + _,_,doEmote = strfind(tmp, "^/(.+)") + if doEmote then + _,_,NextQueuedDoEmote = doEmote + skipsound = true + else + NextQueuedEmote = tmp + end + end + tmp = db["SoundCritHealth"] + if not skipsound and tmp ~= "DISABLED" then + PlaySoundFile( (tmp ~= nil and (SoundPath .. tmp .. ".wav") ) or DEFAULT_SoundCritHealth) + end + tmp = db["MsgChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and + db["MsgCritHealth"] ~= "DISABLED" and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ------- + NextQueuedMessage = "<< " .. + (db["MsgCritHealth"] or VitalWatchLocale.DEFAULT_MsgCritHealth) .. + " >>" + NextQueuedMessageChan = db["MsgChan"] or "PARTY" + NextQueuedMessagePriority = 4 + -------- + end + elseif percentage < (db["ThresholdLowHealth"] or DEFAULT_ThresholdLowHealth) and + (not LastPercentageHealth[unit] or + LastPercentageHealth[unit] >= (db["ThresholdLowHealth"] or DEFAULT_ThresholdLowHealth)) then + if db["FlashFrameHealth"] then + AdjustFlashRate(LowHealthFrame, (2.0 * percentage) + 0.5) + flashRestart = true + end + if db["Heartbeat"] then + self:ScheduleRepeatingEvent( "Heartbeat", + PlayHeartbeatSound, + (2.0 * percentage) + 0.5 ) + end + self:MessageLogUpdate(VitalWatchLocale.Floating_Message_Self_LowHealth, + db["ColourLowHealthR"] or 1, + db["ColourLowHealthG"] or 0, + db["ColourLowHealthB"] or 0) + if db["MessageFrameHealth"] ~= 5 then + MessageFrameHealth_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + local skipsound + tmp = db["EmoteLowHealth"] + if tmp and not playerInBG then + local doEmote + _,_,doEmote = strfind(tmp, "^/(.+)") + if doEmote then + _,_,NextQueuedDoEmote = doEmote + skipsound = true + else + NextQueuedEmote = tmp + end + end + tmp = db["SoundLowHealth"] + if not skipsound and tmp ~= "DISABLED" then + PlaySoundFile( (tmp ~= nil and (SoundPath .. tmp .. ".wav") ) or DEFAULT_SoundLowHealth) + end + tmp = db["MsgChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and NextQueuedPriority == nil and + db["MsgLowHealth"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ----- + NextQueuedMessage = "<< " .. + db["MsgLowHealth"] .. + " >>" + NextQueuedMessageChan = db["MsgChan"] or "PARTY" + NextQueuedMessagePriority = 3 + ----- + end + end + if db["FlashFrameHealth"] and not flashRestart then + StopFrameFlash(LowHealthFrame) + end + LastPercentageHealth[unit] = percentage + elseif UnitIsPlayer(unit) then + local _,c = UnitClass(unit) + local name = (UnitName(unit)) or "Name Unknown" + if ((db["HealthWatch"] and db["HealthWatch"] > 1 and UnitInParty(unit)) or + (db["HealthWatch"] == 3 and UnitInRaid(unit))) and + (not db["HealthNoWatchClass"] or not db["HealthNoWatchClass"][c]) and + (not db["HealthNoWatchName"] or not db["HealthNoWatchName"][name]) then + ------- + self:ProcessUnitHealthOther(unit, name, class) + ------- + end + elseif db["PetHealthWatch"] ~= 1 and unit == "pet" then + self:ProcessUnitHealthOther(unit, (UnitName(unit)) or "Name Unknown", nil, VitalWatchLocale.MyPetTag) + elseif (db["PetHealthWatch"] and db["PetHealthWatch"] > 1 and UnitPlayerOrPetInParty(unit)) or + (db["PetHealthWatch"] == 3 and UnitPlayerOrPetInRaid(unit)) then + self:ProcessUnitHealthOther(unit, (UnitName(unit)) or "Name Unknown", nil, VitalWatchLocale.PetTag) + end + end +end + +function VitalWatch:UNIT_MANA(unit) + if unit and unit ~= "target" and unit ~= "focus" and UnitAffectingCombat(unit) and + UnitMana(unit) > 0 and not UnitIsDeadOrGhost(unit) and UnitPowerType(unit) == 0 then + if unit == "player" or UnitIsUnit(unit, "player") then + local flashRestart + local percentage = UnitMana(unit) / UnitManaMax(unit) + if not percentage then return end + if percentage < (db["ThresholdCritMana"] or DEFAULT_ThresholdCritMana) and + (not LastPercentageMana[unit] or + LastPercentageMana[unit] >= (db["ThresholdCritMana"] or DEFAULT_ThresholdCritMana)) then + if db["FlashFrameMana"] then + AdjustFlashRate(OutOfControlFrame, (2.0 * percentage) + 0.5) + flashRestart = true + end + self:MessageLogUpdate(VitalWatchLocale.Floating_Message_Self_CritMana, + db["ColourCritManaR"] or 1, + db["ColourCritManaG"] or 0, + db["ColourCritManaB"] or 0) + if db["MessageFrameMana"] ~= 5 then + MessageFrameMana_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + local skipsound + tmp = db["EmoteCritMana"] + if tmp ~= "DISABLED" and not playerInBG then + local doEmote + _,_,doEmote = strfind(tmp or VitalWatchLocale.DEFAULT_EmoteCritMana, "^/(.+)") + if doEmote then + NextQueuedDoEmote = doEmote + skipsound = true + else + NextQueuedEmote = tmp + end + end + tmp = db["SoundCritMana"] + if not skipsound and tmp then + PlaySoundFile( SoundPath .. tmp .. ".wav" ) + end + tmp = db["MsgChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and + db["MsgCritMana"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ------- + NextQueuedMessage = "<< " .. + db["MsgCritMana"] .. + " >>" + NextQueuedMessageChan = db["MsgChan"] or "PARTY" + NextQueuedMessagePriority = 3 + -------- + end + elseif (percentage < (db["ThresholdLowMana"] or DEFAULT_ThresholdLowMana)) and + (not LastPercentageMana[unit] or + LastPercentageMana[unit] >= (db["ThresholdLowMana"] or DEFAULT_ThresholdLowMana)) then + if db["FlashFrameMana"] then + AdjustFlashRate(OutOfControlFrame, (2.0 * percentage) + 0.5) + flashRestart = true + end + self:MessageLogUpdate(VitalWatchLocale.Floating_Message_Self_LowMana, + db["ColourLowManaR"] or 1, + db["ColourLowManaG"] or 0, + db["ColourLowManaB"] or 0) + if db["MessageFrameMana"] ~= 5 then + MessageFrameMana_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + local skipsound + tmp = db["EmoteLowMana"] + if tmp and not playerInBG then + local doEmote + _,_,doEmote = strfind(tmp, "^/(.+)") + if doEmote then + _,_,NextQueuedDoEmote = doEmote + skipsound = true + else + NextQueuedEmote = tmp + end + end + tmp = db["SoundLowMana"] + if not skipsound and tmp then + PlaySoundFile( SoundPath .. tmp .. ".wav" ) + end + tmp = db["MsgChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and + (not NextQueuedMessagePriority or NextQueuedMessagePriority < 3) and + db["MsgLowMana"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ----- + NextQueuedMessage = "<< " .. + db["MsgLowMana"] .. + " >>" + NextQueuedMessageChan = db["MsgChan"] or "PARTY" + NextQueuedMessagePriority = 2 + ----- + end + end + if db["FlashFrameMana"] and not flashRestart then + StopFrameFlash(OutOfControlFrame) + end + LastPercentageMana[unit] = percentage + elseif UnitIsPlayer(unit) then + local _,c = UnitClass(unit) + local name = (UnitName(unit)) or "Name Unknown" + if ((db["ManaWatch"] and db["ManaWatch"] > 1 and UnitInParty(unit)) or + (db["ManaWatch"] == 3 and UnitInRaid(unit))) and + (not db["ManaNoWatchClass"] or not db["ManaNoWatchClass"][c]) and + (not db["ManaNoWatchName"] or not db["ManaNoWatchName"][name]) then + local percentage = UnitMana(unit) / UnitManaMax(unit) + if percentage < (db["ThresholdCritMana"] or DEFAULT_ThresholdCritMana) and + (not LastPercentageMana[unit] or + LastPercentageMana[unit] >= (db["ThresholdCritMana"] or DEFAULT_ThresholdCritMana)) then + if c and ClassColors[c] then + self:MessageLogUpdate("|cFF" .. ClassColors[c] .. name .. "|r" .. + VitalWatchLocale.Floating_Message_CritMana, + db["ColourCritManaOtherR"] or 0, + db["ColourCritManaOtherG"] or 0, + db["ColourCritManaOtherB"] or 1) + else + self:MessageLogUpdate(name .. VitalWatchLocale.Floating_Message_CritMana, + db["ColourCritManaOtherR"] or 0, + db["ColourCritManaOtherG"] or 0, + db["ColourCritManaOtherB"] or 1) + end + if db["MessageFrameMana"] ~= 5 then + MessageFrameMana_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + tmp = db["SoundCritManaByClass"] + tmp = tmp ~= nil and tmp[class] + if tmp then + PlaySoundFile( DEFAULT_SoundCritHealthByClass[class] ) + else + tmp = db["SoundCritManaOther"] + if tmp then + PlaySoundFile( SoundPath .. tmp .. ".wav") + end + end + tmp = db["MsgChanOther"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and + (not NextQueuedMessagePriority or NextQueuedMessagePriority < 3) and + db["MsgOtherCritMana"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ------- + NextQueuedMessage = "<< " .. + name .. + db["MsgOtherCritMana"] .. + " (" .. UnitClass(unit) .. + ") >>" + NextQueuedMessageChan = db["MsgChanOther"] or "PARTY" + NextQueuedMessagePriority = 2 + -------- + end + elseif (percentage < (db["ThresholdLowMana"] or DEFAULT_ThresholdLowMana)) and + (not LastPercentageMana[unit] or + LastPercentageMana[unit] >= (db["ThresholdLowMana"] or DEFAULT_ThresholdLowMana)) then + if c and ClassColors[c] then + self:MessageLogUpdate("|cFF" .. ClassColors[c] .. name .. "|r" .. + VitalWatchLocale.Floating_Message_LowMana, + db["ColourLowManaOtherR"] or 0, + db["ColourLowManaOtherG"] or 0, + db["ColourLowManaOtherB"] or 1) + else + self:MessageLogUpdate(name .. VitalWatchLocale.Floating_Message_LowMana, + db["ColourLowManaOtherR"] or 0, + db["ColourLowManaOtherG"] or 0, + db["ColourLowManaOtherB"] or 1) + end + if db["MessageFrameMana"] ~= 5 then + MessageFrameMana_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + tmp = db["SoundLowManaOther"] + if tmp then + PlaySoundFile( SoundPath .. tmp .. ".wav" ) + end + tmp = db["MsgChanOther"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and NextQueuedMessagePriority == nil and + db["MsgOtherLowMana"] and not playerInBG and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + ------- + NextQueuedMessage = "<< " .. + name .. + db["MsgOtherLowMana"] .. + " (" .. UnitClass(unit) .. + ") >>" + NextQueuedMessageChan = db["MsgChanOther"] or "PARTY" + NextQueuedMessagePriority = nil + -------- + end + end + LastPercentageMana[unit] = percentage + end -- big-ass if statement + end + end +end + +------------------------------------------------------------------- +-- Custom Events (Banzai) + + +function VitalWatch:Banzai_UnitGainedAggro( unitId, unitTable ) + if unitId and unitTable and (db["AggroWatch"] ~= 1 or GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0) then + local ru = roster:GetUnitObjectFromUnit(unitId) + if ru then + if unitId == "player" or UnitIsUnit(unitId, "player") then + if db["AggroWatch"] ~= 4 and db["AggroWatch"] ~= 5 then + if not ru.lastAggroAlert or ((GetTime() - ru.lastAggroAlert) > (db["AggroWatchRate"] or DEFAULT_AggroWatchRate)) then + + ru.lastAggroAlert = GetTime() + + if db["FlashFrameAggro"] then + local rate + if ru.banzaiModifier and ru.banzaiModifier > 0 then rate = 10 / ru.banzaiModifier else rate = 1 end + if db["FlashFrameAggro"] == 1 then + AdjustFlashRate( LowHealthFrame, (2.0 * rate) + 0.5) + else + AdjustFlashRate( OutOfControlFrame, (2.0 * rate) + 0.5) + end + end + + local tname,tid = UnitName(unitTable[1]) or "?", unitTable[1] or "?" + VitalWatch:MessageLogUpdate(VitalWatchLocale.Floating_Message_Aggro .. + tname .. + " (" .. + (ru.banzaiModifier or "!") .. + ")", + db["ColourAggroR"] or 0.7, + db["ColourAggroG"] or 0.7, + db["ColourAggroB"] or 0.7) + + if db["MessageFrameAggro"] then + MessageFrameAggro_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + local skipsound + tmp = db["EmoteAggro"] + if tmp and not playerInBG then + local doEmote + _,_,doEmote = strfind(tmp, "^/(.+)") + if doEmote then + _,_,NextQueuedDoEmote = doEmote + skipsound = true + else + NextQueuedEmote = tmp + end + end + tmp = db["MsgAggroChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and db["MsgAggro"] and not playerInBG and + (NextQueuedMessagePriority == nil or (ru.banzaiModifier and ru.banzaiModifier > 20)) and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) and not IsMounted() then + NextQueuedMessage = "<< (" .. + UnitClass("player") .. + ") " .. + db["MsgAggro"] .. + " " .. + tname .. + " (" .. + (ru.banzaiModifier or "!") .. + ") >>" + NextQueuedMessageChan = db["MsgAggroChan"] or "PARTY" + NextQueuedMessagePriority = nil + end + if not skipsound and db["SoundAggro"] then + PlaySoundFile( SoundPath .. db["SoundAggro"] .. ".wav" ) + end + end + end + + -- other players + elseif ((db["AggroWatch"] > 4) or (db["AggroWatch"] > 2 and UnitInParty(unitId))) and + (not db["AggroNoWatchClass"] or not db["AggroNoWatchClass"][ru.class]) and + (not db["AggroNoWatchName"] or not db["AggroNoWatchName"][ru.name]) and + (not ru.lastAggroAlert or + ((GetTime() - ru.lastAggroAlert) > (db["AggroWatchOtherRate"] or DEFAULT_AggroWatchOtherRate))) then + + ru.lastAggroAlert = GetTime() + + local tname,tid = UnitName(unitTable[1]) or "?", unitTable[1] or "?" + if ru.class and ClassColors[ru.class] then + self:MessageLogUpdate("|cFF" .. ClassColors[ru.class] .. ru.name .. "|r" .. + " -- " .. + VitalWatchLocale.Floating_Message_Aggro .. + tname .. + " (" .. + (ru.banzaiModifier or "!") .. + ")", + db["ColourAggroOtherR"] or 0.5, + db["ColourAggroOtherG"] or 0.5, + db["ColourAggroOtherB"] or 0.5) + else + self:MessageLogUpdate(ru.name .. + " -- " .. + VitalWatchLocale.Floating_Message_Aggro .. + tname .. + " (" .. + (ru.banzaiModifier or "!") .. + ")", + db["ColourAggroOtherR"] or 0.5, + db["ColourAggroOtherG"] or 0.5, + db["ColourAggroOtherB"] or 0.5) + end + + if db["MessageFrameAggro"] then + MessageFrameAggro_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + tmp = db["MsgAggroOtherChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and db["MsgAggroOther"] and not playerInBG and + (NextQueuedMessagePriority == nil or (ru.banzaiModifier and ru.banzaiModifier > 30)) and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + NextQueuedMessage = "<< " .. + ru.name .. + " (" .. + UnitClass(unitId) .. + " ) " .. + db["MsgAggroOther"] .. + " " .. + tname .. + " (" .. + (ru.banzaiModifier or "!") .. + ") >>" + NextQueuedMessageChan = db["MsgAggroOtherChan"] or "PARTY" + NextQueuedMessagePriority = nil + end + tmp = db["SoundAggroByClass"] + tmp = tmp ~= nil and ru.class ~= nil and tmp[ru.class] + if tmp then + PlaySoundFile( DEFAULT_SoundAggroByClass[class] ) + else + tmp = db["SoundAggroOther"] + if tmp then + PlaySoundFile( SoundPath .. tmp .. ".wav") + end + end + end + end + end +end + +function VitalWatch:Banzai_UnitLostAggro( unitId ) + if unitId and (db["AggroLostWatch"] ~= 1 or GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0) then + local ru = roster:GetUnitObjectFromUnit(unitId) + if ru then + if unitId == "player" or UnitIsUnit(unitId, "player") then + if db["AggroWatch"] ~= 4 and db["AggroWatch"] ~= 5 then + if db["FlashFrameAggro"] then + if db["FlashFrameAggro"] == 1 then + StopFrameFlash( LowHealthFrame ) + else + StopFrameFlash( OutOfControlFrame ) + end + end + + VitalWatch:MessageLogUpdate(VitalWatchLocale.Floating_Message_Aggro .. + VitalWatchLocale.Floating_Message_AggroLost .. + " (" .. + (ru.banzaiModifier or "-") .. + ")", + db["ColourAggroLostR"] or 1, + db["ColourAggroLostG"] or 1, + db["ColourAggroLostB"] or 1) + + if db["MessageFrameAggro"] then + MessageFrameAggro_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + local skipsound + tmp = db["EmoteAggroLost"] + if tmp and not playerInBG then + local doEmote + _,_,doEmote = strfind(tmp, "^/(.+)") + if doEmote then + _,_,NextQueuedDoEmote = doEmote + skipsound = true + else + NextQueuedEmote = tmp + end + end + tmp = db["MsgAggroChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and db["MsgAggroLost"] and not playerInBG and + NextQueuedMessagePriority == nil and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) and not IsMounted() then + NextQueuedMessage = "<< (" .. + UnitClass("player") .. + ") " .. + db["MsgAggroLost"] .. + " >> (" .. + (ru.banzaiModifier or "-") .. + ")" + NextQueuedMessageChan = db["MsgAggroChan"] or "PARTY" + NextQueuedMessagePriority = nil + end + if not skipsound and db["SoundAggroLost"] then + PlaySoundFile( SoundPath .. db["SoundAggroLost"] .. ".wav" ) + end + end + -- other players + elseif ((db["AggroWatch"] > 4) or (db["AggroWatch"] > 2 and GetNumPartyMembers() > 0)) and + (not db["AggroNoWatchClass"] or not db["AggroNoWatchClass"][ru.class]) and + (not db["AggroNoWatchName"] or not db["AggroNoWatchName"][ru.name]) then + + if ru.class and ClassColors[ru.class] then + self:MessageLogUpdate("|cFF" .. ClassColors[ru.class] .. ru.name .. "|r" .. + " -- " .. + VitalWatchLocale.Floating_Message_Aggro .. + VitalWatchLocale.Floating_Message_AggroLost .. + " (" .. + (ru.banzaiModifier or "-") .. + ")", + db["ColourAggroLostOtherR"] or 1, + db["ColourAggroLostOtherG"] or 1, + db["ColourAggroLostOtherB"] or 1) + else + self:MessageLogUpdate(ru.name .. + " -- " .. + VitalWatchLocale.Floating_Message_Aggro .. + VitalWatchLocale.Floating_Message_AggroLost .. + " (" .. + (ru.banzaiModifier or "-") .. + ")", + db["ColourAggroLostOtherR"] or 1, + db["ColourAggroLostOtherG"] or 1, + db["ColourAggroLostOtherB"] or 1) + end + if db["MessageFrameAggro"] then + MessageFrameAggro_AddMessage(MessageFrame, + MessageLog[NumMessageLog].text, + MessageLog[NumMessageLog].r, + MessageLog[NumMessageLog].g, + MessageLog[NumMessageLog].b, + MessageFrameMessageAlpha, + MessageFrameHoldTime) + end + + tmp = db["MsgAggroOtherChan"] + if not db["DisableMsg"] and tmp ~= "DISABLED" and db["MsgAggroLostOther"] and not playerInBG and + NextQueuedMessagePriority == nil and + ((tmp and tmp ~= "PARTY" and tmp ~= "RAID") or + ((not tmp or tmp == "PARTY") and GetNumPartyMembers() > 0) or + (tmp == "RAID" and GetNumRaidMembers() > 0)) then + NextQueuedMessage = "<< " .. + ru.name .. + " (" .. + UnitClass(unitId) .. + " ) " .. + db["MsgAggroLostOther"] .. + " (" .. + (ru.banzaiModifier or "-") .. + ") >>" + NextQueuedMessageChan = db["MsgAggroOtherChan"] or "PARTY" + NextQueuedMessagePriority = nil + end + if db["SoundAggroLostOther"] then + PlaySoundFile( SoundPath .. db["SoundAggroLostOther"] .. ".wav" ) + end + end + end + end +end + + +------------------------------------------------------------------- +-- Timed Events + +function VitalWatch:SendQueuedMessages() + if NextQueuedMessage and NextQueuedMessageChan then + SendChatMessage( NextQueuedMessage, NextQueuedMessageChan) + NextQueuedMessage = nil + NextQueuedMessageChan = nil + NextQueuedMessagePriority = nil + end + if NextQueuedEmote then + SendChatMessage( NextQueuedEmote, "EMOTE" ) + NextQueuedEmote = nil + end + if NextQueuedDoEmote then + DoEmote( NextQueuedDoEmote ) + NextQueuedDoEmote = nil + end +end + +function VitalWatch:StopTimers() + NextQueuedMessage = nil + NextQueuedEmote = nil + NextQueuedDoEmote = nil + NextQueuedMessageChan = nil + NextQueuedMessagePriority = nil + if db["FlashFrameHealth"] then StopFrameFlash(LowHealthFrame) end + if db["FlashFrameMana"] then StopFrameFlash(OutOfControlFrame) end + if db["FlashFrameAggro"] then + if db["FlashFrameAggro"] == 1 then + StopFrameFlash( LowHealthFrame ) + else + StopFrameFlash( OutOfControlFrame ) + end + end + self:CancelScheduledEvent("Heartbeat") +end + +function VitalWatch:FadeOldMessagesInLog() + if NumMessageLog > 0 then + local numRecent = 0 + local currTime = GetTime() + local fadeTime = db["MessageLogFade"] or 15 + for i = 1,NumMessageLog do + if currTime < MessageLog[i].time + fadeTime then + numRecent = numRecent + 1 + end + end + if numRecent ~= NumMessageLog then + local oldi + for i = 1,numRecent do + oldi = NumMessageLog - numRecent + i + MessageLog[i].text = MessageLog[oldi].text + MessageLog[i].ftext = MessageLog[oldi].ftext + MessageLog[i].r = MessageLog[oldi].r + MessageLog[i].g = MessageLog[oldi].g + MessageLog[i].b = MessageLog[oldi].b + MessageLog[i].time = MessageLog[oldi].time + end + NumMessageLog = numRecent + self:UpdateText() + self:UpdateTooltip() + end + end +end + + +----------------------------------------------------------------------- +-- Tablet / Message Log + +function VitalWatch:OnTooltipUpdate() + local cat = tablet:AddCategory("columns", 1, "showWithoutChildren", true, "hideBlankLine", false) + if MessageLog and NumMessageLog > 0 then + for i = 1,NumMessageLog do + cat:AddLine('text', MessageLog[i].ftext, + 'textR', MessageLog[i].r, + 'textG', MessageLog[i].g, + 'textB', MessageLog[i].b, + 'wrap', false) + end + end +end + +function VitalWatch:OnTextUpdate() + if NumMessageLog > 0 then + self:SetText( "VW: " .. MessageLog[NumMessageLog].text ) + else + self:SetText( "VitalWatch" ) + end +end + +function VitalWatch:OnMenuRequest(level, value) + self:LoDRun( "VitalWatchOptions", "CreateDDMenu", level, value) +end + +function VitalWatch:MessageLogUpdate(text, r, g, b) + local max = (db["MaxMessages"] or 6) + if NumMessageLog == max then + for i = max,2,-1 do + MessageLog[i-1].text = MessageLog[i].text + MessageLog[i-1].ftext = MessageLog[i].ftext + MessageLog[i-1].r = MessageLog[i].r + MessageLog[i-1].g = MessageLog[i].g + MessageLog[i-1].b = MessageLog[i].b + MessageLog[i-1].time = MessageLog[i].time + end + MessageLog[max].text = text + MessageLog[max].r = r + MessageLog[max].g = g + MessageLog[max].b = b + MessageLog[max].time = GetTime() + MessageLog[max].ftext = "[" .. date('*t').sec .. "s] " .. text + else + NumMessageLog = NumMessageLog + 1 + if not MessageLog[NumMessageLog] then MessageLog[NumMessageLog] = {} end + MessageLog[NumMessageLog].text = text + MessageLog[NumMessageLog].r = r + MessageLog[NumMessageLog].g = g + MessageLog[NumMessageLog].b = b + MessageLog[NumMessageLog].time = GetTime() + MessageLog[NumMessageLog].ftext = "[" .. date('*t').sec .. "s] " .. text + end + self:UpdateTooltip() + self:UpdateText() +end + + +---------------------------------------------------------------- +-- Initialization + +-- oRA2 additions via BanzaiAlert by vhaarr +--[[ +local function InitoRA() + oRAPAggroAlert = oRA:NewModule(VitalWatchLocale.oRAModule) + oRAPAggroAlert.participant = true + oRAPAggroAlert.name = VitalWatchLocale.oRAModuleName +end + +function VitalWatch:ADDON_LOADED() + if arg1 == "oRA" and oRA and not oRAPAggroAlert then + InitoRA() + end +end +--]] +function VitalWatch:OnInitialize() + self:RegisterDB("VitalWatchDB", "VitalWatchCharDB") + db = self.db.profile +end + +function VitalWatch:OnDataUpdate() + db = self.db.profile +end + +function VitalWatch:OnEnable() + LastPercentageHealth = {} + LastPercentageMana = {} + MessageLog = {} + self:ReInitialize() +--[[ + if oRA and not oRAPAggroAlert then + InitoRA() + end + --]] +end + +function VitalWatch:ReInitialize() + self:UnregisterAllEvents() + self:CancelAllScheduledEvents() + db = self.db.profile + -- self:RegisterEvent("ADDON_LOADED") + if db["HealthWatch"] ~= 1 then + self:RegisterEvent("UNIT_HEALTH") + end + if db["ManaWatch"] ~= 1 then + self:RegisterEvent("UNIT_MANA") + end + + if db["FlashFrameHealth"] or db["FlashFrameMana"] or db["Heartbeat"] or db["FlashFrameAggro"] then + self:RegisterEvent("PLAYER_REGEN_ENABLED", "StopTimers") + self:RegisterEvent("PLAYER_REGEN_DISABLED", "StopTimers") + end + + if db["MessageFrameHealth"] then + if db["MessageFrameHealth"] == 1 then -- SCT + if SCT_Display_Message then + MessageFrameHealth_AddMessage = SCT4_AddMessage + elseif SCT and SCT_MSG_FRAME and SCT_MSG_FRAME.AddMessage then + MessageFrameHealth_AddMessage = SCT_AddMessage + end + elseif db["MessageFrameHealth"] == 2 and MikSBT and MikSBT.DisplayMessage then -- MikSBT + MessageFrameHealth_AddMessage = MikSBT_AddMessage + elseif db["MessageFrameHealth"] == 3 and CombatText_AddMessage then -- Blizzard's Floating Combat Text + MessageFrameHealth_AddMessage = Blizzard_FCT_AddMessage + elseif db["MessageFrameHealth"] == 4 then -- BigWigs + MessageFrameHealth_AddMessage = BigWigs_AddMessage + end + else + CreateMessageFrame() + MessageFrameHealth_AddMessage = MessageFrame.AddMessage + end + if db["MessageFrameMana"] then + if db["MessageFrameMana"] == 1 then -- SCT + if SCT_Display_Message then + MessageFrameMana_AddMessage = SCT4_AddMessage + elseif SCT and SCT_MSG_FRAME and SCT_MSG_FRAME.AddMessage then + MessageFrameMana_AddMessage = SCT_AddMessage + end + elseif db["MessageFrameMana"] == 2 and MikSBT and MikSBT.DisplayMessage then -- MikSBT + MessageFrameMana_AddMessage = MikSBT_AddMessage + elseif db["MessageFrameMana"] == 3 and CombatText_AddMessage then -- Blizzard's Floating Combat Text + MessageFrameMana_AddMessage = Blizzard_FCT_AddMessage + elseif db["MessageFrameMana"] == 4 then -- BigWigs + MessageFrameMana_AddMessage = BigWigs_AddMessage + end + else + CreateMessageFrame() + MessageFrameMana_AddMessage = MessageFrame.AddMessage + end + if db["MessageFrameAggro"] then + if db["MessageFrameAggro"] == 1 then -- SCT + if SCT_Display_Message then + MessageFrameAggro_AddMessage = SCT4_AddMessage + elseif SCT and SCT_MSG_FRAME and SCT_MSG_FRAME.AddMessage then + MessageFrameAggro_AddMessage = SCT_AddMessage + end + elseif db["MessageFrameAggro"] == 2 and MikSBT and MikSBT.DisplayMessage then -- MikSBT + MessageFrameAggro_AddMessage = MikSBT_AddMessage + elseif db["MessageFrameAggro"] == 3 and CombatText_AddMessage then -- Blizzard's Floating Combat Text + MessageFrameAggro_AddMessage = Blizzard_FCT_AddMessage + elseif db["MessageFrameAggro"] == 4 then -- BigWigs + MessageFrameAggro_AddMessage = BigWigs_AddMessage + elseif db["MessageFrameAggro"] == 5 then + CreateMessageFrame() + MessageFrameAggro_AddMessage = MessageFrame.AddMessage + end + end + + if not db["EnableInBGs"] then + self:RegisterEvent("PLAYER_ENTERING_WORLD") + end + self:ScheduleRepeatingEvent("QueuedMessages", self.SendQueuedMessages, db["MsgRate"] or 0.2, self) + self:ScheduleRepeatingEvent("FadeOldMessagesInLog", self.FadeOldMessagesInLog, db["MessageLogFade"] or 5, self) + + -- BanzaiLib + if db["AggroWatch"] then + if not roster then roster = AceLibrary("Roster-2.1") end + self:RegisterEvent("Banzai_UnitGainedAggro") + end + if db["AggroLostWatch"] then + if not roster then roster = AceLibrary("Roster-2.1") end + self:RegisterEvent("Banzai_UnitLostAggro") + end +end + +function VitalWatch:OnDisable() + self:UnregisterAllEvents() + self:CancelAllScheduledEvents() + playerInBG = nil + LastHealthPercentage = nil + LastManaPercentage = nil + NumMessageLog = 0 + MessageLog = nil + roster = nil +end diff --git a/VitalWatch.toc b/VitalWatch.toc new file mode 100644 index 0000000..bce65e1 --- /dev/null +++ b/VitalWatch.toc @@ -0,0 +1,19 @@ +## Interface: 20003 +## Author: Thrae +## Title: VitalWatch |cff7fff7f -Ace2-|r +## Notes: A powerfully customizable health, mana, and aggro alert system for solo, group, and raid. +## SavedVariables: VitalWatchDB +## SavedVariablesPerCharacter: VitalWatchCharDB +libs\AceLibrary\AceLibrary.lua +libs\AceOO-2.0\AceOO-2.0.lua +libs\AceEvent-2.0\AceEvent-2.0.lua +libs\AceDB-2.0\AceDB-2.0.lua +libs\AceAddon-2.0\AceAddon-2.0.lua +libs\Dewdrop-2.0\Dewdrop-2.0.lua +libs\Tablet-2.0\Tablet-2.0.lua +libs\FuBarPlugin-2.0\FuBarPlugin-2.0.lua +libs\Roster-2.1\Roster-2.1.lua +libs\Banzai-1.1\Banzai-1.1.lua + +VitalWatchLocale_enEN.lua +VitalWatch.lua diff --git a/VitalWatchLocale_enEN.lua b/VitalWatchLocale_enEN.lua new file mode 100644 index 0000000..de28c1c --- /dev/null +++ b/VitalWatchLocale_enEN.lua @@ -0,0 +1,41 @@ +--[[ VitalWatch by Thrae +-- +-- +-- English Localization (Default) +-- +-- VitalWatchLocale should be defined in your FIRST localization +-- code. +-- +--]] + +VitalWatchLocale = {} +VitalWatchLocale.locale = getglobal("GetLocale")() + +if VitalWatchLocale.locale then + VitalWatchLocale.LogTitle = "VitalWatch Log" + + VitalWatchLocale.Floating_Message_Self_LowHealth = "Your health is low!" + VitalWatchLocale.Floating_Message_Self_CritHealth = "Your health is CRITICAL!" + VitalWatchLocale.Floating_Message_Self_LowMana = "Your mana is low!" + VitalWatchLocale.Floating_Message_Self_CritMana = "Your mana is CRITICAL!" + + VitalWatchLocale.Floating_Message_LowHealth = "'s health is low!" + VitalWatchLocale.Floating_Message_CritHealth = "'s health is CRITICAL!" + VitalWatchLocale.Floating_Message_LowMana = "'s mana is low!" + VitalWatchLocale.Floating_Message_CritMana = "'s mana is CRITICAL!" + + VitalWatchLocale.Floating_Message_Aggro = "AGGRO: " + VitalWatchLocale.Floating_Message_AggroLost = "Lost aggro." + + VitalWatchLocale.MyPetTag = " (my pet)" + VitalWatchLocale.PetTag = " (pet)" + + VitalWatchLocale.DEFAULT_MsgCritHealth = "My health is CRITICAL!" + + -- Below are voice emotes for your locale. On an English client, you + -- would type in /oom to announce to everyone you're out of mana. To + -- properly translate, you must find out the command for your locale. + VitalWatchLocale.DEFAULT_EmoteCritMana = "/oom" + + VitalWatchLocale.locale = nil -- we no longer need this +end diff --git a/libs/AceAddon-2.0/AceAddon-2.0.lua b/libs/AceAddon-2.0/AceAddon-2.0.lua new file mode 100644 index 0000000..f9eabff --- /dev/null +++ b/libs/AceAddon-2.0/AceAddon-2.0.lua @@ -0,0 +1,1102 @@ +--[[ +Name: AceAddon-2.0 +Revision: $Rev: 23383 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/wiki/AceAddon-2.0 +SVN: http://svn.wowace.com/wowace/trunk/Ace2/AceAddon-2.0 +Description: Base for all Ace addons to inherit from. +Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, (optional) AceConsole-2.0 +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "AceAddon-2.0" +local MINOR_VERSION = "$Revision: 23383 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end + +local function safecall(func,...) + local success, err = pcall(func,...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("\n(.-: )in.-\n") or "") .. err) end +end +-- Localization +local STANDBY, TITLE, NOTES, VERSION, AUTHOR, DATE, CATEGORY, EMAIL, CREDITS, WEBSITE, CATEGORIES, ABOUT, LICENSE, PRINT_ADDON_INFO +if GetLocale() == "deDE" then + STANDBY = "|cffff5050(Standby)|r" -- capitalized + + TITLE = "Titel" + NOTES = "Anmerkung" + VERSION = "Version" + AUTHOR = "Autor" + DATE = "Datum" + CATEGORY = "Kategorie" + EMAIL = "E-Mail" + WEBSITE = "Webseite" + CREDITS = "Credits" -- fix + LICENSE = "License" -- fix + + ABOUT = "Über" + PRINT_ADDON_INFO = "Gibt Addondaten aus" + + CATEGORIES = { + ["Action Bars"] = "Aktionsleisten", + ["Auction"] = "Auktion", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Schlachtfeld/PvP", + ["Buffs"] = "Stärkungszauber", + ["Chat/Communication"] = "Chat/Kommunikation", + ["Druid"] = "Druide", + ["Hunter"] = "Jäger", + ["Mage"] = "Magier", + ["Paladin"] = "Paladin", + ["Priest"] = "Priester", + ["Rogue"] = "Schurke", + ["Shaman"] = "Schamane", + ["Warlock"] = "Hexenmeister", + ["Warrior"] = "Krieger", + ["Healer"] = "Heiler", + ["Tank"] = "Tank", + ["Caster"] = "Zauberer", + ["Combat"] = "Kampf", + ["Compilations"] = "Zusammenstellungen", + ["Data Export"] = "Datenexport", + ["Development Tools"] = "Entwicklungs Tools", + ["Guild"] = "Gilde", + ["Frame Modification"] = "Frame Veränderungen", + ["Interface Enhancements"] = "Interface Verbesserungen", + ["Inventory"] = "Inventar", + ["Library"] = "Bibliotheken", + ["Map"] = "Karte", + ["Mail"] = "Post", + ["Miscellaneous"] = "Diverses", + ["Quest"] = "Quest", + ["Raid"] = "Schlachtzug", + ["Tradeskill"] = "Beruf", + ["UnitFrame"] = "Einheiten-Fenster", + } +elseif GetLocale() == "frFR" then + STANDBY = "|cffff5050(attente)|r" + + TITLE = "Titre" + NOTES = "Notes" + VERSION = "Version" + AUTHOR = "Auteur" + DATE = "Date" + CATEGORY = "Catégorie" + EMAIL = "E-mail" + WEBSITE = "Site web" + CREDITS = "Credits" -- fix + LICENSE = "License" -- fix + + ABOUT = "A propos" + PRINT_ADDON_INFO = "Afficher les informations sur l'addon" + + CATEGORIES = { + ["Action Bars"] = "Barres d'action", + ["Auction"] = "Hôtel des ventes", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Champs de bataille/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druide", + ["Hunter"] = "Chasseur", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Prêtre", + ["Rogue"] = "Voleur", + ["Shaman"] = "Chaman", + ["Warlock"] = "Démoniste", + ["Warrior"] = "Guerrier", + ["Healer"] = "Soigneur", + ["Tank"] = "Tank", + ["Caster"] = "Casteur", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Exportation de données", + ["Development Tools"] = "Outils de développement", + ["Guild"] = "Guilde", + ["Frame Modification"] = "Modification des fenêtres", + ["Interface Enhancements"] = "Améliorations de l'interface", + ["Inventory"] = "Inventaire", + ["Library"] = "Bibliothèques", + ["Map"] = "Carte", + ["Mail"] = "Courrier", + ["Miscellaneous"] = "Divers", + ["Quest"] = "Quêtes", + ["Raid"] = "Raid", + ["Tradeskill"] = "Métiers", + ["UnitFrame"] = "Fenêtres d'unité", + } +elseif GetLocale() == "koKR" then + STANDBY = "|cffff5050(사용가능)|r" + + TITLE = "제목" + NOTES = "노트" + VERSION = "버전" + AUTHOR = "저작자" + DATE = "날짜" + CATEGORY = "분류" + EMAIL = "E-mail" + WEBSITE = "웹사이트" + CREDITS = "Credits" -- fix + LICENSE = "License" -- fix + + ABOUT = "정보" + PRINT_ADDON_INFO = "애드온 정보 출력" + + CATEGORIES = { + ["Action Bars"] = "액션바", + ["Auction"] = "경매", + ["Audio"] = "음향", + ["Battlegrounds/PvP"] = "전장/PvP", + ["Buffs"] = "버프", + ["Chat/Communication"] = "대화/의사소통", + ["Druid"] = "드루이드", + ["Hunter"] = "사냥꾼", + ["Mage"] = "마법사", + ["Paladin"] = "성기사", + ["Priest"] = "사제", + ["Rogue"] = "도적", + ["Shaman"] = "주술사", + ["Warlock"] = "흑마법사", + ["Warrior"] = "전사", + ["Healer"] = "힐러", + ["Tank"] = "탱커", + ["Caster"] = "캐스터", + ["Combat"] = "전투", + ["Compilations"] = "복합", + ["Data Export"] = "자료 출력", + ["Development Tools"] = "개발 도구", + ["Guild"] = "길드", + ["Frame Modification"] = "구조 변경", + ["Interface Enhancements"] = "인터페이스 강화", + ["Inventory"] = "인벤토리", + ["Library"] = "라이브러리", + ["Map"] = "지도", + ["Mail"] = "우편", + ["Miscellaneous"] = "기타", + ["Quest"] = "퀘스트", + ["Raid"] = "공격대", + ["Tradeskill"] = "전문기술", + ["UnitFrame"] = "유닛 프레임", + } +elseif GetLocale() == "zhTW" then + STANDBY = "|cffff5050(待命)|r" + + TITLE = "標題" + NOTES = "註記" + VERSION = "版本" + AUTHOR = "作者" + DATE = "日期" + CATEGORY = "類別" + EMAIL = "E-mail" + WEBSITE = "網站" + CREDITS = "Credits" -- fix + LICENSE = "License" -- fix + + ABOUT = "關於" + PRINT_ADDON_INFO = "顯示插件資訊" + + CATEGORIES = { + ["Action Bars"] = "動作列", + ["Auction"] = "拍賣", + ["Audio"] = "音樂", + ["Battlegrounds/PvP"] = "戰場/PvP", + ["Buffs"] = "增益", + ["Chat/Communication"] = "聊天/通訊", + ["Druid"] = "德魯伊", + ["Hunter"] = "獵人", + ["Mage"] = "法師", + ["Paladin"] = "聖騎士", + ["Priest"] = "牧師", + ["Rogue"] = "盜賊", + ["Shaman"] = "薩滿", + ["Warlock"] = "術士", + ["Warrior"] = "戰士", + ["Healer"] = "治療者", + ["Tank"] = "坦克", + ["Caster"] = "施法者", + ["Combat"] = "戰鬥", + ["Compilations"] = "編輯", + ["Data Export"] = "資料匯出", + ["Development Tools"] = "開發工具", + ["Guild"] = "公會", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "介面增強", + ["Inventory"] = "背包", + ["Library"] = "資料庫", + ["Map"] = "地圖", + ["Mail"] = "郵件", + ["Miscellaneous"] = "綜合", + ["Quest"] = "任務", + ["Raid"] = "團隊", + ["Tradeskill"] = "商業技能", + ["UnitFrame"] = "單位框架", + } +elseif GetLocale() == "zhCN" then + STANDBY = "|cffff5050(暂挂)|r" + + TITLE = "标题" + NOTES = "附注" + VERSION = "版本" + AUTHOR = "作者" + DATE = "日期" + CATEGORY = "分类" + EMAIL = "电子邮件" + WEBSITE = "网站" + CREDITS = "Credits" -- fix + LICENSE = "License" -- fix + + ABOUT = "关于" + PRINT_ADDON_INFO = "印列出插件信息" + + CATEGORIES = { + ["Action Bars"] = "动作条", + ["Auction"] = "拍卖", + ["Audio"] = "音频", + ["Battlegrounds/PvP"] = "战场/PvP", + ["Buffs"] = "增益魔法", + ["Chat/Communication"] = "聊天/交流", + ["Druid"] = "德鲁伊", + ["Hunter"] = "猎人", + ["Mage"] = "法师", + ["Paladin"] = "圣骑士", + ["Priest"] = "牧师", + ["Rogue"] = "盗贼", + ["Shaman"] = "萨满祭司", + ["Warlock"] = "术士", + ["Warrior"] = "战士", +-- ["Healer"] = "治疗保障", +-- ["Tank"] = "近战控制", +-- ["Caster"] = "远程输出", + ["Combat"] = "战斗", + ["Compilations"] = "编译", + ["Data Export"] = "数据导出", + ["Development Tools"] = "开发工具", + ["Guild"] = "公会", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "界面增强", + ["Inventory"] = "背包", + ["Library"] = "库", + ["Map"] = "地图", + ["Mail"] = "邮件", + ["Miscellaneous"] = "杂项", + ["Quest"] = "任务", + ["Raid"] = "团队", + ["Tradeskill"] = "商业技能", + ["UnitFrame"] = "头像框架", + } +elseif GetLocale() == "esES" then + STANDBY = "|cffff5050(espera)|r" + + TITLE = "Título" + NOTES = "Notas" + VERSION = "Versión" + AUTHOR = "Autor" + DATE = "Fecha" + CATEGORY = "Categoría" + EMAIL = "E-mail" + WEBSITE = "Web" + CREDITS = "Créditos" + LICENSE = "License" -- fix + + ABOUT = "Acerca de" + PRINT_ADDON_INFO = "Muestra información acerca del accesorio." + + CATEGORIES = { + ["Action Bars"] = "Barras de Acción", + ["Auction"] = "Subasta", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Campos de Batalla/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Comunicación", + ["Druid"] = "Druida", + ["Hunter"] = "Cazador", + ["Mage"] = "Mago", + ["Paladin"] = "Paladín", + ["Priest"] = "Sacerdote", + ["Rogue"] = "Pícaro", + ["Shaman"] = "Chamán", + ["Warlock"] = "Brujo", + ["Warrior"] = "Guerrero", + ["Healer"] = "Sanador", + ["Tank"] = "Tanque", + ["Caster"] = "Conjurador", + ["Combat"] = "Combate", + ["Compilations"] = "Compilaciones", + ["Data Export"] = "Exportar Datos", + ["Development Tools"] = "Herramientas de Desarrollo", + ["Guild"] = "Hermandad", + ["Frame Modification"] = "Modificación de Marcos", + ["Interface Enhancements"] = "Mejoras de la Interfaz", + ["Inventory"] = "Inventario", + ["Library"] = "Biblioteca", + ["Map"] = "Mapa", + ["Mail"] = "Correo", + ["Miscellaneous"] = "Misceláneo", + ["Quest"] = "Misión", + ["Raid"] = "Banda", + ["Tradeskill"] = "Habilidad de Comercio", + ["UnitFrame"] = "Marco de Unidades", + } +else -- enUS + STANDBY = "|cffff5050(standby)|r" + + TITLE = "Title" + NOTES = "Notes" + VERSION = "Version" + AUTHOR = "Author" + DATE = "Date" + CATEGORY = "Category" + EMAIL = "E-mail" + WEBSITE = "Website" + CREDITS = "Credits" + LICENSE = "License" + + ABOUT = "About" + PRINT_ADDON_INFO = "Show information about the addon." + + CATEGORIES = { + ["Action Bars"] = "Action Bars", + ["Auction"] = "Auction", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Battlegrounds/PvP", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druid", + ["Hunter"] = "Hunter", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Priest", + ["Rogue"] = "Rogue", + ["Shaman"] = "Shaman", + ["Warlock"] = "Warlock", + ["Warrior"] = "Warrior", + ["Healer"] = "Healer", + ["Tank"] = "Tank", + ["Caster"] = "Caster", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Data Export", + ["Development Tools"] = "Development Tools", + ["Guild"] = "Guild", + ["Frame Modification"] = "Frame Modification", + ["Interface Enhancements"] = "Interface Enhancements", + ["Inventory"] = "Inventory", + ["Library"] = "Library", + ["Map"] = "Map", + ["Mail"] = "Mail", + ["Miscellaneous"] = "Miscellaneous", + ["Quest"] = "Quest", + ["Raid"] = "Raid", + ["Tradeskill"] = "Tradeskill", + ["UnitFrame"] = "UnitFrame", + } +end + +setmetatable(CATEGORIES, { __index = function(self, key) -- case-insensitive + local lowerKey = key:lower() + for k,v in pairs(CATEGORIES) do + if k:lower() == lowerKey then + return v + end + end +end }) + +-- Create the library object + +local AceOO = AceLibrary("AceOO-2.0") +local AceAddon = AceOO.Class() +local AceEvent +local AceConsole +local AceModuleCore + +function AceAddon:GetLocalizedCategory(name) + self:argCheck(name, 2, "string") + return CATEGORIES[name] or UNKNOWN +end + +function AceAddon:ToString() + return "AceAddon" +end + +local function print(text) + DEFAULT_CHAT_FRAME:AddMessage(text) +end + +function AceAddon:ADDON_LOADED(name) + local unregister = true + local initAddon = {} + while #self.nextAddon > 0 do + local addon = table.remove(self.nextAddon, 1) + if addon.possibleNames[name] then + table.insert(initAddon, addon) + else + unregister = nil + table.insert(self.skipAddon, addon) + end + end + self.nextAddon, self.skipAddon = self.skipAddon, self.nextAddon + if unregister then + AceAddon:UnregisterEvent("ADDON_LOADED") + end + while #initAddon > 0 do + local addon = table.remove(initAddon, 1) + table.insert(self.addons, addon) + if not self.addons[name] then + self.addons[name] = addon + end + addon.possibleNames = nil + self:InitializeAddon(addon, name) + end +end + +local function RegisterOnEnable(self) + if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage then -- HACK + AceAddon.playerLoginFired = true + end + if AceAddon.playerLoginFired then + AceAddon.addonsStarted[self] = true + if (type(self.IsActive) ~= "function" or self:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(self) or AceModuleCore:IsModuleActive(self)) then + AceAddon.addonsEnabled[self] = true + local current = self.class + while true do + if current == AceOO.Class or not current then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable,mixin,self,true) + end + end + end + current = current.super + end + if type(self.OnEnable) == "function" then + safecall(self.OnEnable,self,true) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", self, true) + end + end + else + if not AceAddon.addonsToOnEnable then + AceAddon.addonsToOnEnable = {} + end + table.insert(AceAddon.addonsToOnEnable, self) + end +end + +function AceAddon:InitializeAddon(addon, name) + if addon.name == nil then + addon.name = name + end + if GetAddOnMetadata then + -- TOC checks + if addon.title == nil then + addon.title = GetAddOnMetadata(name, "Title") + end + if type(addon.title) == "string" then + local num = addon.title:find(" |cff7fff7f %-Ace2%-|r$") + if num then + addon.title = addon.title:sub(1, num - 1) + end + addon.title = addon.title:trim() + end + if addon.notes == nil then + addon.notes = GetAddOnMetadata(name, "Notes") + end + if type(addon.notes) == "string" then + addon.notes = addon.notes:trim() + end + if addon.version == nil then + addon.version = GetAddOnMetadata(name, "Version") + end + if type(addon.version) == "string" then + if addon.version:find("%$Revision: (%d+) %$") then + addon.version = addon.version:gsub("%$Revision: (%d+) %$", "%1") + elseif addon.version:find("%$Rev: (%d+) %$") then + addon.version = addon.version:gsub("%$Rev: (%d+) %$", "%1") + elseif addon.version:find("%$LastChangedRevision: (%d+) %$") then + addon.version = addon.version:gsub("%$LastChangedRevision: (%d+) %$", "%1") + end + addon.version = addon.version:trim() + end + if addon.author == nil then + addon.author = GetAddOnMetadata(name, "Author") + end + if type(addon.author) == "string" then + addon.author = addon.author:trim() + end + if addon.credits == nil then + addon.credits = GetAddOnMetadata(name, "X-Credits") + end + if type(addon.credits) == "string" then + addon.credits = addon.credits:trim() + end + if addon.date == nil then + addon.date = GetAddOnMetadata(name, "X-Date") or GetAddOnMetadata(name, "X-ReleaseDate") + end + if type(addon.date) == "string" then + if addon.date:find("%$Date: (.-) %$") then + addon.date = addon.date:gsub("%$Date: (.-) %$", "%1") + elseif addon.date:find("%$LastChangedDate: (.-) %$") then + addon.date = addon.date:gsub("%$LastChangedDate: (.-) %$", "%1") + end + addon.date = addon.date:trim() + end + + if addon.category == nil then + addon.category = GetAddOnMetadata(name, "X-Category") + end + if type(addon.category) == "string" then + addon.category = addon.category:trim() + end + if addon.email == nil then + addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email") + end + if type(addon.email) == "string" then + addon.email = addon.email:trim() + end + if addon.license == nil then + addon.license = GetAddOnMetadata(name, "X-License") + end + if type(addon.license) == "string" then + addon.license = addon.license:trim() + end + if addon.website == nil then + addon.website = GetAddOnMetadata(name, "X-Website") + end + if type(addon.website) == "string" then + addon.website = addon.website:trim() + end + end + local current = addon.class + while true do + if current == AceOO.Class or not current then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedInitialize) == "function" then + mixin:OnEmbedInitialize(addon, name) + end + end + end + current = current.super + end + if type(addon.OnInitialize) == "function" then + safecall(addon.OnInitialize, addon, name) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonInitialized", addon) + end + RegisterOnEnable(addon) +end + +local function isGoodVariable(var) + return type(var) == "string" or type(var) == "number" +end +function AceAddon.prototype:PrintAddonInfo() + local x + if isGoodVariable(self.title) then + x = "|cffffff7f" .. tostring(self.title) .. "|r" + elseif isGoodVariable(self.name) then + x = "|cffffff7f" .. tostring(self.name) .. "|r" + else + x = "|cffffff7f<" .. tostring(self.class) .. " instance>|r" + end + if type(self.IsActive) == "function" then + if not self:IsActive() then + x = x .. " " .. STANDBY + end + end + if isGoodVariable(self.version) then + x = x .. " - |cffffff7f" .. tostring(self.version) .. "|r" + end + if isGoodVariable(self.notes) then + x = x .. " - " .. tostring(self.notes) + end + print(x) + if isGoodVariable(self.author) then + print(" - |cffffff7f" .. AUTHOR .. ":|r " .. tostring(self.author)) + end + if isGoodVariable(self.credits) then + print(" - |cffffff7f" .. CREDITS .. ":|r " .. tostring(self.credits)) + end + if isGoodVariable(self.date) then + print(" - |cffffff7f" .. DATE .. ":|r " .. tostring(self.date)) + end + if self.category then + local category = CATEGORIES[self.category] + if category then + print(" - |cffffff7f" .. CATEGORY .. ":|r " .. category) + end + end + if isGoodVariable(self.email) then + print(" - |cffffff7f" .. EMAIL .. ":|r " .. tostring(self.email)) + end + if isGoodVariable(self.website) then + print(" - |cffffff7f" .. WEBSITE .. ":|r " .. tostring(self.website)) + end + if isGoodVariable(self.license) then + print(" - |cffffff7f" .. LICENSE .. ":|r " .. tostring(self.license)) + end +end + +local options +function AceAddon:GetAceOptionsDataTable(target) + if not options then + options = { + about = { + name = ABOUT, + desc = PRINT_ADDON_INFO, + type = "execute", + func = "PrintAddonInfo", + order = -1, + } + } + end + return options +end + +function AceAddon:PLAYER_LOGIN() + self.playerLoginFired = true + if self.addonsToOnEnable then + while #self.addonsToOnEnable > 0 do + local addon = table.remove(self.addonsToOnEnable, 1) + self.addonsStarted[addon] = true + if (type(addon.IsActive) ~= "function" or addon:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(addon) or AceModuleCore:IsModuleActive(addon)) then + self.addonsEnabled[addon] = true + local current = addon.class + while true do + if current == AceOO.Class or not current then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable, mixin, addon, true) + end + end + end + current = current.super + end + if type(addon.OnEnable) == "function" then + safecall(addon.OnEnable, addon, true) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", addon, true) + end + end + end + self.addonsToOnEnable = nil + end +end + +function AceAddon.prototype:Inject(t) + AceAddon:argCheck(t, 2, "table") + for k,v in pairs(t) do + self[k] = v + end +end + +function AceAddon.prototype:init() + if not AceEvent then + error(MAJOR_VERSION .. " requires AceEvent-2.0", 4) + end + AceAddon.super.prototype.init(self) + + self.super = self.class.prototype + + AceAddon:RegisterEvent("ADDON_LOADED", "ADDON_LOADED") + local names = {} + for i = 1, GetNumAddOns() do + if IsAddOnLoaded(i) then names[GetAddOnInfo(i)] = true end + end + self.possibleNames = names + table.insert(AceAddon.nextAddon, self) +end + +function AceAddon.prototype:ToString() + local x + if type(self.title) == "string" then + x = self.title + elseif type(self.name) == "string" then + x = self.name + else + x = "<" .. tostring(self.class) .. " instance>" + end + if (type(self.IsActive) == "function" and not self:IsActive()) or (AceModuleCore and AceModuleCore:IsModule(addon) and AceModuleCore:IsModuleActive(addon)) then + x = x .. " " .. STANDBY + end + return x +end + +AceAddon.new = function(self, ...) + local class = AceAddon:pcall(AceOO.Classpool, self, ...) + return class:new() +end + +local function external(self, major, instance) + if major == "AceEvent-2.0" then + AceEvent = instance + + AceEvent:embed(self) + + self:RegisterEvent("PLAYER_LOGIN", "PLAYER_LOGIN", true) + elseif major == "AceConsole-2.0" then + AceConsole = instance + + local slashCommands = { "/ace2" } + local _,_,_,enabled,loadable = GetAddOnInfo("Ace") + if not enabled or not loadable then + table.insert(slashCommands, "/ace") + end + local function listAddon(addon, depth) + if not depth then + depth = 0 + end + + local s = (" "):rep(depth) .. " - " .. tostring(addon) + if rawget(addon, 'version') then + s = s .. " - |cffffff7f" .. tostring(addon.version) .. "|r" + end + if rawget(addon, 'slashCommand') then + s = s .. " |cffffff7f(" .. tostring(addon.slashCommand) .. ")|r" + end + print(s) + if type(rawget(addon, 'modules')) == "table" then + local i = 0 + for k,v in pairs(addon.modules) do + i = i + 1 + if i == 6 then + print((" "):rep(depth + 1) .. " - more...") + break + else + listAddon(v, depth + 1) + end + end + end + end + local function listNormalAddon(i) + local name,_,_,enabled,loadable = GetAddOnInfo(i) + if not loadable then + enabled = false + end + if self.addons[name] then + listAddon(self.addons[name]) + else + local s = " - " .. tostring(GetAddOnMetadata(i, "Title") or name) + local version = GetAddOnMetadata(i, "Version") + if version then + if version:find("%$Revision: (%d+) %$") then + version = version:gsub("%$Revision: (%d+) %$", "%1") + elseif version:find("%$Rev: (%d+) %$") then + version = version:gsub("%$Rev: (%d+) %$", "%1") + elseif version:find("%$LastChangedRevision: (%d+) %$") then + version = version:gsub("%$LastChangedRevision: (%d+) %$", "%1") + end + s = s .. " - |cffffff7f" .. version .. "|r" + end + if not enabled then + s = s .. " |cffff0000(disabled)|r" + end + if IsAddOnLoadOnDemand(i) then + s = s .. " |cff00ff00[LoD]|r" + end + print(s) + end + end + local function mySort(alpha, bravo) + return tostring(alpha) < tostring(bravo) + end + AceConsole.RegisterChatCommand(self, slashCommands, { + desc = "AddOn development framework", + name = "Ace2", + type = "group", + args = { + about = { + desc = "Get information about Ace2", + name = "About", + type = "execute", + func = function() + print("|cffffff7fAce2|r - |cffffff7f2.0." .. MINOR_VERSION:gsub("%$Revision: (%d+) %$", "%1") .. "|r - AddOn development framework") + print(" - |cffffff7f" .. AUTHOR .. ":|r Ace Development Team") + print(" - |cffffff7f" .. WEBSITE .. ":|r http://www.wowace.com/") + end + }, + list = { + desc = "List addons", + name = "List", + type = "group", + args = { + ace2 = { + desc = "List addons using Ace2", + name = "Ace2", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + table.sort(self.addons, mySort) + for _,v in ipairs(self.addons) do + listAddon(v) + end + end + }, + all = { + desc = "List all addons", + name = "All", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local count = GetNumAddOns() + for i = 1, count do + listNormalAddon(i) + end + end + }, + enabled = { + desc = "List all enabled addons", + name = "Enabled", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local count = GetNumAddOns() + for i = 1, count do + local _,_,_,enabled,loadable = GetAddOnInfo(i) + if enabled and loadable then + listNormalAddon(i) + end + end + end + }, + disabled = { + desc = "List all disabled addons", + name = "Disabled", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local count = GetNumAddOns() + for i = 1, count do + local _,_,_,enabled,loadable = GetAddOnInfo(i) + if not enabled or not loadable then + listNormalAddon(i) + end + end + end + }, + lod = { + desc = "List all LoadOnDemand addons", + name = "LoadOnDemand", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local count = GetNumAddOns() + for i = 1, count do + if IsAddOnLoadOnDemand(i) then + listNormalAddon(i) + end + end + end + }, + ace1 = { + desc = "List all addons using Ace1", + name = "Ace 1.x", + type = "execute", + func = function() + print("|cffffff7fAddon list:|r") + local count = GetNumAddOns() + for i = 1, count do + local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i) + if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then + listNormalAddon(i) + end + end + end + }, + libs = { + desc = "List all libraries using AceLibrary", + name = "Libraries", + type = "execute", + func = function() + if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then + print("|cffffff7fLibrary list:|r") + for name, data in pairs(AceLibrary.libs) do + local s + if data.minor then + s = " - " .. tostring(name) .. "." .. tostring(data.minor) + else + s = " - " .. tostring(name) + end + if rawget(AceLibrary(name), 'slashCommand') then + s = s .. " |cffffff7f(" .. tostring(AceLibrary(name).slashCommand) .. "|cffffff7f)" + end + print(s) + end + end + end + }, + search = { + desc = "Search by name", + name = "Search", + type = "text", + usage = "", + input = true, + get = false, + set = function(...) + local arg = { ... } + for i,v in ipairs(arg) do + arg[i] = v:gsub('%*', '.*'):gsub('%%', '%%%%'):lower() + end + local count = GetNumAddOns() + for i = 1, count do + local name = GetAddOnInfo(i) + local good = true + for _,v in ipairs(arg) do + if not name:lower():find(v) then + good = false + break + end + end + if good then + listNormalAddon(i) + end + end + end + } + }, + }, + enable = { + desc = "Enable addon", + name = "Enable", + type = "text", + usage = "", + get = false, + set = function(text) + local name,title,_,enabled,_,reason = GetAddOnInfo(text) + if reason == "MISSING" then + print(("|cffffff7fAce2:|r AddOn %q does not exist"):format(text)) + elseif not enabled then + EnableAddOn(text) + print(("|cffffff7fAce2:|r %s is now enabled"):format(title or name)) + else + print(("|cffffff7fAce2:|r %s is already enabled"):format(title or name)) + end + end, + }, + disable = { + desc = "Disable addon", + name = "Disable", + type = "text", + usage = "", + get = false, + set = function(text) + local name,title,_,enabled,_,reason = GetAddOnInfo(text) + if reason == "MISSING" then + print(("|cffffff7fAce2:|r AddOn %q does not exist"):format(text)) + elseif enabled then + DisableAddOn(text) + print(("|cffffff7fAce2:|r %s is now disabled"):format(title or name)) + else + print(("|cffffff7fAce2:|r %s is already disabled"):format(title or name)) + end + end, + }, + load = { + desc = "Load addon", + name = "Load", + type = "text", + usage = "", + get = false, + set = function(text) + local name,title,_,_,loadable,reason = GetAddOnInfo(text) + if reason == "MISSING" then + print(("|cffffff7fAce2:|r AddOn %q does not exist."):format(text)) + elseif not loadable then + print(("|cffffff7fAce2:|r AddOn %q is not loadable. Reason: %s"):format(text, reason)) + else + LoadAddOn(text) + print(("|cffffff7fAce2:|r %s is now loaded"):format(title or name)) + end + end + }, + info = { + desc = "Display information", + name = "Information", + type = "execute", + func = function() + local mem, threshold = gcinfo() + print((" - |cffffff7fMemory usage [|r%.3f MiB|cffffff7f]|r"):format(mem / 1024)) + if threshold then + print((" - |cffffff7fThreshold [|r%.3f MiB|cffffff7f]|r"):format(threshold / 1024)) + end + print((" - |cffffff7fFramerate [|r%.0f fps|cffffff7f]|r"):format(GetFramerate())) + local bandwidthIn, bandwidthOut, latency = GetNetStats() + bandwidthIn, bandwidthOut = floor(bandwidthIn * 1024), floor(bandwidthOut * 1024) + print((" - |cffffff7fLatency [|r%.0f ms|cffffff7f]|r"):format(latency)) + print((" - |cffffff7fBandwidth in [|r%.0f B/s|cffffff7f]|r"):format(bandwidthIn)) + print((" - |cffffff7fBandwidth out [|r%.0f B/s|cffffff7f]|r"):format(bandwidthOut)) + print((" - |cffffff7fTotal addons [|r%d|cffffff7f]|r"):format(GetNumAddOns())) + print((" - |cffffff7fAce2 addons [|r%d|cffffff7f]|r"):format(#self.addons)) + local ace = 0 + local enabled = 0 + local disabled = 0 + local lod = 0 + for i = 1, GetNumAddOns() do + local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i) + if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then + ace = ace + 1 + end + if IsAddOnLoadOnDemand(i) then + lod = lod + 1 + end + local _,_,_,IsActive,loadable = GetAddOnInfo(i) + if not IsActive or not loadable then + disabled = disabled + 1 + else + enabled = enabled + 1 + end + end + print((" - |cffffff7fAce 1.x addons [|r%d|cffffff7f]|r"):format(ace)) + print((" - |cffffff7fLoadOnDemand addons [|r%d|cffffff7f]|r"):format(lod)) + print((" - |cffffff7fenabled addons [|r%d|cffffff7f]|r"):format(enabled)) + print((" - |cffffff7fdisabled addons [|r%d|cffffff7f]|r"):format(disabled)) + local libs = 0 + if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then + for _ in pairs(AceLibrary.libs) do + libs = libs + 1 + end + end + print((" - |cffffff7fAceLibrary instances [|r%d|cffffff7f]|r"):format(libs)) + end + } + } + }) + elseif major == "AceModuleCore-2.0" then + AceModuleCore = instance + end +end + +local function activate(self, oldLib, oldDeactivate) + AceAddon = self + + self.playerLoginFired = oldLib and oldLib.playerLoginFired or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage + self.addonsToOnEnable = oldLib and oldLib.addonsToOnEnable + self.addons = oldLib and oldLib.addons or {} + self.nextAddon = oldLib and oldLib.nextAddon or {} + self.skipAddon = oldLib and oldLib.skipAddon or {} + self.addonsStarted = oldLib and oldLib.addonsStarted or {} + self.addonsEnabled = oldLib and oldLib.addonsEnabled or {} + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceAddon, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) diff --git a/libs/AceDB-2.0/AceDB-2.0.lua b/libs/AceDB-2.0/AceDB-2.0.lua new file mode 100644 index 0000000..3bea2e9 --- /dev/null +++ b/libs/AceDB-2.0/AceDB-2.0.lua @@ -0,0 +1,1984 @@ +--[[ +Name: AceDB-2.0 +Revision: $Rev: 23704 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceDB-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceDB-2.0 +Description: Mixin to allow for fast, clean, and featureful saved variable + access. +Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0 +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "AceDB-2.0" +local MINOR_VERSION = "$Revision: 23704 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end + +local function safecall(func,...) + local success, err = pcall(func,...) + if not success then geterrorhandler()(err) end +end + +local ACTIVE, ENABLED, STATE, TOGGLE_ACTIVE, MAP_ACTIVESUSPENDED, SET_PROFILE, SET_PROFILE_USAGE, PROFILE, PLAYER_OF_REALM, CHOOSE_PROFILE_DESC, CHOOSE_PROFILE_GUI, COPY_PROFILE_DESC, COPY_PROFILE_GUI, OTHER_PROFILE_DESC, OTHER_PROFILE_GUI, OTHER_PROFILE_USAGE, CHARACTER_COLON, REALM_COLON, CLASS_COLON, DEFAULT, ALTERNATIVE + +if GetLocale() == "deDE" then + ACTIVE = "Aktiv" + ENABLED = "Aktiviert" + STATE = "Status" + TOGGLE_ACTIVE = "Stoppt/Aktiviert dieses Addon." + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Aktiv|r", [false] = "|cffff0000Gestoppt|r" } + SET_PROFILE = "Setzt das Profil f\195\188r dieses Addon." + SET_PROFILE_USAGE = "{Charakter || Klasse || Realm || }" + PROFILE = "Profil" + PLAYER_OF_REALM = "%s von %s" + CHOOSE_PROFILE_DESC = "W\195\164hle ein Profil." + CHOOSE_PROFILE_GUI = "W\195\164hle" + COPY_PROFILE_DESC = "Kopiert Einstellungen von einem anderem Profil." + COPY_PROFILE_GUI = "Kopiere von" + OTHER_PROFILE_DESC = "W\195\164hle ein anderes Profil." + OTHER_PROFILE_GUI = "Anderes" + OTHER_PROFILE_USAGE = "" + + CHARACTER_COLON = "Charakter: " + REALM_COLON = "Realm: " + CLASS_COLON = "Klasse: " + + DEFAULT = "Default" -- fix + ALTERNATIVE = "Alternative" -- fix +elseif GetLocale() == "frFR" then + ACTIVE = "Actif" + ENABLED = "Activ\195\169" + STATE = "Etat" + TOGGLE_ACTIVE = "Suspend/active cet addon." + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Actif|r", [false] = "|cffff0000Suspendu|r" } + SET_PROFILE = "S\195\169lectionne le profil pour cet addon." + SET_PROFILE_USAGE = "{perso || classe || royaume || }" + PROFILE = "Profil" + PLAYER_OF_REALM = "%s de %s" + CHOOSE_PROFILE_DESC = "Choisissez un profil." + CHOOSE_PROFILE_GUI = "Choix" + COPY_PROFILE_DESC = "Copier les param\195\168tres d'un autre profil." + COPY_PROFILE_GUI = "Copier \195\160 partir de" + OTHER_PROFILE_DESC = "Choisissez un autre profil." + OTHER_PROFILE_GUI = "Autre" + OTHER_PROFILE_USAGE = "" + + CHARACTER_COLON = "Personnage: " + REALM_COLON = "Royaume: " + CLASS_COLON = "Classe: " + + DEFAULT = "Default" -- fix + ALTERNATIVE = "Alternative" -- fix +elseif GetLocale() == "koKR" then + ACTIVE = "활성화" + ENABLED = "활성화" + STATE = "상태" + TOGGLE_ACTIVE = "이 애드온 중지/계속 실행" + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00활성화|r", [false] = "|cffff0000중지됨|r" } + SET_PROFILE = "이 애드온에 프로필 설정" + SET_PROFILE_USAGE = "{캐릭터명 || 직업 || 서버명 || <프로필명>}" + PROFILE = "프로필" + PLAYER_OF_REALM = "%s (%s 서버)" + CHOOSE_PROFILE_DESC = "프로파일을 선택합니다." + CHOOSE_PROFILE_GUI = "선택" + COPY_PROFILE_DESC = "다른 프로파일에서 설정을 복사합니다." + COPY_PROFILE_GUI = "복사" + OTHER_PROFILE_DESC = "다른 프로파일을 선택합니다." + OTHER_PROFILE_GUI = "기타" + OTHER_PROFILE_USAGE = "<프로파일명>" + + CHARACTER_COLON = "캐릭터: " + REALM_COLON = "서버: " + CLASS_COLON = "직업: " + + DEFAULT = "Default" -- fix + ALTERNATIVE = "Alternative" -- fix +elseif GetLocale() == "zhTW" then + ACTIVE = "啟動" + ENABLED = "啟用" + STATE = "狀態" + TOGGLE_ACTIVE = "暫停/重啟這個插件。" + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00啟動|r", [false] = "|cffff0000已暫停|r" } + SET_PROFILE = "設定這插件的記錄檔。" + SET_PROFILE_USAGE = "{角色 || 聯業 || 伺服器 || <記錄檔名稱>}" + PROFILE = "記錄檔" + PLAYER_OF_REALM = "%s 於 %s" + CHOOSE_PROFILE_DESC = "選擇一個記錄檔" + CHOOSE_PROFILE_GUI = "選擇" + COPY_PROFILE_DESC = "由其他記錄檔複製設定。" + COPY_PROFILE_GUI = "複製由" + OTHER_PROFILE_DESC = "選擇其他記錄檔。" + OTHER_PROFILE_GUI = "其他" + OTHER_PROFILE_USAGE = "<記錄檔名稱>" + + CHARACTER_COLON = "角色:" + REALM_COLON = "伺服器:" + CLASS_COLON = "聯業:" + + DEFAULT = "Default" -- fix + ALTERNATIVE = "Alternative" -- fix +elseif GetLocale() == "zhCN" then + ACTIVE = "\230\156\137\230\149\136" + ENABLED = "\229\144\175\231\148\168" + STATE = "\231\138\182\230\128\129" + TOGGLE_ACTIVE = "\230\154\130\229\129\156/\230\129\162\229\164\141 \230\173\164\230\143\146\228\187\182." + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00\230\156\137\230\149\136|r", [false] = "|cffff0000\230\154\130\229\129\156|r" } + SET_PROFILE = "\232\174\190\231\189\174\233\133\141\231\189\174\230\150\135\228\187\182\228\184\186\232\191\153\230\143\146\228\187\182." + SET_PROFILE_USAGE = "{\229\173\151\231\172\166 || \233\128\137\228\187\182\231\177\187 || \229\159\159 || <\233\133\141\231\189\174\230\150\135\228\187\182\229\144\141\229\173\151>}" + PROFILE = "\233\133\141\231\189\174\230\150\135\228\187\182" + PLAYER_OF_REALM = "%s \231\154\132 %s" + CHOOSE_PROFILE_DESC = "\233\128\137\230\139\169\233\133\141\231\189\174\230\150\135\228\187\182." + CHOOSE_PROFILE_GUI = "\233\128\137\230\139\169" + COPY_PROFILE_DESC = "\229\164\141\229\136\182\232\174\190\231\189\174\228\187\142\229\143\166\228\184\128\228\184\170\233\133\141\231\189\174\230\150\135\228\187\182." + COPY_PROFILE_GUI = "\229\164\141\229\136\182\228\187\142" + OTHER_PROFILE_DESC = "\233\128\137\230\139\169\229\143\166\228\184\128\228\184\170\233\133\141\231\189\174\230\150\135\228\187\182." + OTHER_PROFILE_GUI = "\229\133\182\228\187\150" + OTHER_PROFILE_USAGE = "<\233\133\141\231\189\174\230\150\135\228\187\182\229\144\141\229\173\151>" + + CHARACTER_COLON = "\229\173\151\231\172\166: " + REALM_COLON = "\229\159\159: " + CLASS_COLON = "\233\128\137\228\187\182\231\177\187: " + + DEFAULT = "Default" -- fix + ALTERNATIVE = "Alternative" -- fix +elseif GetLocale() == "esES" then + ACTIVE = "Activo" + ENABLED = "Activado" + STATE = "Estado" + TOGGLE_ACTIVE = "Parar/Continuar este accesorio" + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Activo|r", [false] = "|cffff0000Parado|r" } + SET_PROFILE = "Selecciona el perfil para este accesorio." + SET_PROFILE_USAGE = "{perso || clase || reino || }" + PROFILE = "Perfil" + PLAYER_OF_REALM = "%s de %s" + CHOOSE_PROFILE_DESC = "Elige un perfil." + CHOOSE_PROFILE_GUI = "Elige" + COPY_PROFILE_DESC = "Copiar de un perfil a otro" + COPY_PROFILE_GUI = "Copiar desde" + OTHER_PROFILE_DESC = "Elige otro perfil." + OTHER_PROFILE_GUI = "Otro" + OTHER_PROFILE_USAGE = "" + + CHARACTER_COLON = "Personaje: " + REALM_COLON = "Reino: " + CLASS_COLON = "Clase: " + + DEFAULT = "Por defecto" + ALTERNATIVE = "Alternativo" +else -- enUS + ACTIVE = "Active" + ENABLED = "Enabled" + STATE = "State" + TOGGLE_ACTIVE = "Suspend/resume this addon." + MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Active|r", [false] = "|cffff0000Suspended|r" } + SET_PROFILE = "Set profile for this addon." + SET_PROFILE_USAGE = "{char || class || realm || }" + PROFILE = "Profile" + PLAYER_OF_REALM = "%s of %s" + CHOOSE_PROFILE_DESC = "Choose a profile." + CHOOSE_PROFILE_GUI = "Choose" + COPY_PROFILE_DESC = "Copy settings from another profile." + COPY_PROFILE_GUI = "Copy from" + OTHER_PROFILE_DESC = "Choose another profile." + OTHER_PROFILE_GUI = "Other" + OTHER_PROFILE_USAGE = "" + + CHARACTER_COLON = "Character: " + REALM_COLON = "Realm: " + CLASS_COLON = "Class: " + + DEFAULT = "Default" + ALTERNATIVE = "Alternative" +end +local convertFromOldCharID +do + local matchStr = "^" .. PLAYER_OF_REALM:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1"):gsub("%%s", "(.+)") .. "$" + function convertFromOldCharID(str) + local player, realm = str:match(matchStr) + if not player then + return str + end + return player .. " - " .. realm + end +end + +local AceOO = AceLibrary("AceOO-2.0") +local AceEvent +local Mixin = AceOO.Mixin +local AceDB = Mixin { + "RegisterDB", + "RegisterDefaults", + "ResetDB", + "SetProfile", + "GetProfile", + "CopyProfileFrom", + "ToggleActive", + "IsActive", + "AcquireDBNamespace", + } +local Dewdrop = AceLibrary:HasInstance("Dewdrop-2.0") and AceLibrary("Dewdrop-2.0") + +local _G = getfenv(0) + +local function inheritDefaults(t, defaults) + if not defaults then + return t + end + for k,v in pairs(defaults) do + if k == "*" then + local v = v + if type(v) == "table" then + setmetatable(t, { + __index = function(self, key) + if key == nil then + return nil + end + self[key] = {} + inheritDefaults(self[key], v) + return self[key] + end + } ) + else + setmetatable(t, { + __index = function(self, key) + if key == nil then + return nil + end + self[key] = v + return self[key] + end + } ) + end + for key in pairs(t) do + if (defaults[key] == nil or key == "*") and type(t[key]) == "table" then + inheritDefaults(t[key], v) + end + end + else + if type(v) == "table" then + if type(t[k]) ~= "table" then + t[k] = {} + end + inheritDefaults(t[k], v) + elseif t[k] == nil then + t[k] = v + end + end + end + return t +end + +local _,race = UnitRace("player") +local faction +if race == "Orc" or race == "Scourge" or race == "Troll" or race == "Tauren" or race == "BloodElf" then + faction = FACTION_HORDE +else + faction = FACTION_ALLIANCE +end +local server = GetRealmName():trim() +local charID = UnitName("player") .. " - " .. server +local realmID = server .. " - " .. faction +local classID = UnitClass("player") + +AceDB.CHAR_ID = charID +AceDB.REALM_ID = realmID +AceDB.CLASS_ID = classID + +AceDB.FACTION = faction +AceDB.REALM = server +AceDB.NAME = UnitName("player") + +local new, del +do + local list = setmetatable({}, {__mode="k"}) + function new() + local t = next(list) + if t then + list[t] = nil + return t + else + return {} + end + end + + function del(t) + setmetatable(t, nil) + for k in pairs(t) do + t[k] = nil + end + list[t] = true + end +end + +local caseInsensitive_mt = { + __index = function(self, key) + if type(key) ~= "string" then + return nil + end + local lowerKey = key:lower() + for k,v in pairs(self) do + if k:lower() == lowerKey then + return self[k] + end + end + end, + __newindex = function(self, key, value) + if type(key) ~= "string" then + return error("table index is nil", 2) + end + local lowerKey = key:lower() + for k in pairs(self) do + if k:lower() == lowerKey then + rawset(self, k, nil) + rawset(self, key, value) + return + end + end + rawset(self, key, value) + end +} + +local db_mt = { __index = function(db, key) + if key == "char" then + if db.charName then + if type(_G[db.charName]) ~= "table" then + _G[db.charName] = {} + end + if type(_G[db.charName].global) ~= "table" then + _G[db.charName].global = {} + end + rawset(db, 'char', _G[db.charName].global) + else + if type(db.raw.chars) ~= "table" then + db.raw.chars = {} + end + local id = charID + if type(db.raw.chars[id]) ~= "table" then + db.raw.chars[id] = {} + end + rawset(db, 'char', db.raw.chars[id]) + end + if db.defaults and db.defaults.char then + inheritDefaults(db.char, db.defaults.char) + end + return db.char + elseif key == "realm" then + if type(db.raw.realms) ~= "table" then + db.raw.realms = {} + end + local id = realmID + if type(db.raw.realms[id]) ~= "table" then + db.raw.realms[id] = {} + end + rawset(db, 'realm', db.raw.realms[id]) + if db.defaults and db.defaults.realm then + inheritDefaults(db.realm, db.defaults.realm) + end + return db.realm + elseif key == "server" then + if type(db.raw.servers) ~= "table" then + db.raw.servers = {} + end + local id = server + if type(db.raw.servers[id]) ~= "table" then + db.raw.servers[id] = {} + end + rawset(db, 'server', db.raw.servers[id]) + if db.defaults and db.defaults.server then + inheritDefaults(db.server, db.defaults.server) + end + return db.server + elseif key == "account" then + if type(db.raw.account) ~= "table" then + db.raw.account = {} + end + rawset(db, 'account', db.raw.account) + if db.defaults and db.defaults.account then + inheritDefaults(db.account, db.defaults.account) + end + return db.account + elseif key == "faction" then + if type(db.raw.factions) ~= "table" then + db.raw.factions = {} + end + local id = faction + if type(db.raw.factions[id]) ~= "table" then + db.raw.factions[id] = {} + end + rawset(db, 'faction', db.raw.factions[id]) + if db.defaults and db.defaults.faction then + inheritDefaults(db.faction, db.defaults.faction) + end + return db.faction + elseif key == "class" then + if type(db.raw.classes) ~= "table" then + db.raw.classes = {} + end + local id = classID + if type(db.raw.classes[id]) ~= "table" then + db.raw.classes[id] = {} + end + rawset(db, 'class', db.raw.classes[id]) + if db.defaults and db.defaults.class then + inheritDefaults(db.class, db.defaults.class) + end + return db.class + elseif key == "profile" then + if type(db.raw.profiles) ~= "table" then + db.raw.profiles = setmetatable({}, caseInsensitive_mt) + else + setmetatable(db.raw.profiles, caseInsensitive_mt) + end + local id = db.raw.currentProfile[charID] + if id == "char" then + id = "char/" .. charID + elseif id == "class" then + id = "class/" .. classID + elseif id == "realm" then + id = "realm/" .. realmID + end + if type(db.raw.profiles[id]) ~= "table" then + db.raw.profiles[id] = {} + end + rawset(db, 'profile', db.raw.profiles[id]) + if db.defaults and db.defaults.profile then + inheritDefaults(db.profile, db.defaults.profile) + end + return db.profile + elseif key == "raw" or key == "defaults" or key == "name" or key == "charName" or key == "namespaces" then + return nil + end + error(("Cannot access key %q in db table. You may want to use db.profile[%q]"):format(tostring(key), tostring(key)), 2) +end, __newindex = function(db, key, value) + error(("Cannot access key %q in db table. You may want to use db.profile[%q]"):format(tostring(key), tostring(key)), 2) +end } + +local function RecalculateAceDBCopyFromList(target) + local db = target.db + local t = target['acedb-profile-copylist'] + for k,v in pairs(t) do + t[k] = nil + end + local _,currentProfile = AceDB.GetProfile(target) + if db and db.raw then + if db.raw.profiles then + for k in pairs(db.raw.profiles) do + if currentProfile ~= k then + if k:find("^char/") then + local name = k:sub(6) + local player, realm = name:match("^(.*) %- (.*)$") + if player then + name = PLAYER_OF_REALM:format(player, realm) + end + t[k] = CHARACTER_COLON .. name + elseif k:find("^realm/") then + local name = k:sub(7) + t[k] = REALM_COLON .. name + elseif k:find("^class/") then + local name = k:sub(7) + t[k] = CLASS_COLON .. name + else + t[k] = k + end + end + end + end + if db.raw.namespaces then + for _,n in pairs(db.raw.namespaces) do + if n.profiles then + for k in pairs(n.profiles) do + if currentProfile ~= k then + if k:find('^char/') then + local name = k:sub(6) + local player, realm = name:match("^(.*) %- (.*)$") + if player then + name = PLAYER_OF_REALM:format(player, realm) + end + t[k] = CHARACTER_COLON .. name + elseif k:find('^realm/') then + local name = k:sub(7) + t[k] = REALM_COLON .. name + elseif k:find('^class/') then + local name = k:sub(7) + t[k] = CLASS_COLON .. name + else + t[k] = k + end + end + end + end + end + end + end + if t.Default then + t.Default = DEFAULT + end + if t.Alternative then + t.Alternative = ALTERNATIVE + end +end + +local function RecalculateAceDBProfileList(target) + local t = target['acedb-profile-list'] + for k,v in pairs(t) do + t[k] = nil + end + t.char = CHARACTER_COLON .. PLAYER_OF_REALM:format(UnitName("player"), server) + t.realm = REALM_COLON .. realmID + t.class = CLASS_COLON .. classID + t.Default = DEFAULT + local db = target.db + if db and db.raw then + if db.raw.profiles then + for k in pairs(db.raw.profiles) do + if not k:find("^char/") and not k:find("^realm/") and not k:find("^class/") then + t[k] = k + end + end + end + if db.raw.namespaces then + for _,n in pairs(db.raw.namespaces) do + if n.profiles then + for k in pairs(n.profiles) do + if not k:find("^char/") and not k:find("^realm/") and not k:find("^class/") then + t[k] = k + end + end + end + end + end + local curr = db.raw.currentProfile and db.raw.currentProfile[charID] + if curr and not t[curr] then + t[curr] = curr + end + end + if t.Alternative then + t.Alternative = ALTERNATIVE + end +end + +local CrawlForSerialization +local CrawlForDeserialization + +local function SerializeObject(o) + local t = { o:Serialize() } + CrawlForSerialization(t) + t[0] = o.class:GetLibraryVersion() + return t +end + +local function DeserializeObject(t) + CrawlForDeserialization(t) + local className = t[0] + t[0] = nil + return AceLibrary(className):Deserialize(unpack(t)) +end + +local function IsSerializable(t) + return AceOO.inherits(t, AceOO.Class) and t.class and type(t.class.Deserialize) == "function" and type(t.Serialize) == "function" and type(t.class.GetLibraryVersion) == "function" +end + +function CrawlForSerialization(t) + local tmp = new() + for k,v in pairs(t) do + tmp[k] = v + end + for k,v in pairs(tmp) do + if type(v) == "table" and type(rawget(v, 0)) ~= "userdata" then + if IsSerializable(v) then + v = SerializeObject(v) + t[k] = v + else + CrawlForSerialization(v) + end + end + if type(k) == "table" and type(rawget(k, 0)) ~= "userdata" then + if IsSerializable(k) then + t[k] = nil + t[SerializeObject(k)] = v + else + CrawlForSerialization(k) + end + end + tmp[k] = nil + k = nil + end + tmp = del(tmp) +end + +local function IsDeserializable(t) + return type(rawget(t, 0)) == "string" and AceLibrary:HasInstance(rawget(t, 0)) +end + +function CrawlForDeserialization(t) + local tmp = new() + for k,v in pairs(t) do + tmp[k] = v + end + for k,v in pairs(tmp) do + if type(v) == "table" then + if IsDeserializable(v) then + t[k] = DeserializeObject(v) + del(v) + v = t[k] + elseif type(rawget(v, 0)) ~= "userdata" then + CrawlForDeserialization(v) + end + end + if type(k) == "table" then + if IsDeserializable(k) then + t[k] = nil + t[DeserializeObject(k)] = v + del(k) + elseif type(rawget(k, 0)) ~= "userdata" then + CrawlForDeserialization(k) + end + end + tmp[k] = nil + k = nil + end + tmp = del(tmp) +end + +local namespace_mt = { __index = function(namespace, key) + local db = namespace.db + local name = namespace.name + if key == "char" then + if db.charName then + if type(_G[db.charName]) ~= "table" then + _G[db.charName] = {} + end + if type(_G[db.charName].namespaces) ~= "table" then + _G[db.charName].namespaces = {} + end + if type(_G[db.charName].namespaces[name]) ~= "table" then + _G[db.charName].namespaces[name] = {} + end + rawset(namespace, 'char', _G[db.charName].namespaces[name]) + else + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].chars) ~= "table" then + db.raw.namespaces[name].chars = {} + end + local id = charID + if type(db.raw.namespaces[name].chars[id]) ~= "table" then + db.raw.namespaces[name].chars[id] = {} + end + rawset(namespace, 'char', db.raw.namespaces[name].chars[id]) + end + if namespace.defaults and namespace.defaults.char then + inheritDefaults(namespace.char, namespace.defaults.char) + end + return namespace.char + elseif key == "realm" then + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].realms) ~= "table" then + db.raw.namespaces[name].realms = {} + end + local id = realmID + if type(db.raw.namespaces[name].realms[id]) ~= "table" then + db.raw.namespaces[name].realms[id] = {} + end + rawset(namespace, 'realm', db.raw.namespaces[name].realms[id]) + if namespace.defaults and namespace.defaults.realm then + inheritDefaults(namespace.realm, namespace.defaults.realm) + end + return namespace.realm + elseif key == "server" then + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].servers) ~= "table" then + db.raw.namespaces[name].servers = {} + end + local id = server + if type(db.raw.namespaces[name].servers[id]) ~= "table" then + db.raw.namespaces[name].servers[id] = {} + end + rawset(namespace, 'server', db.raw.namespaces[name].servers[id]) + if namespace.defaults and namespace.defaults.server then + inheritDefaults(namespace.server, namespace.defaults.server) + end + return namespace.server + elseif key == "account" then + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].account) ~= "table" then + db.raw.namespaces[name].account = {} + end + rawset(namespace, 'account', db.raw.namespaces[name].account) + if namespace.defaults and namespace.defaults.account then + inheritDefaults(namespace.account, namespace.defaults.account) + end + return namespace.account + elseif key == "faction" then + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].factions) ~= "table" then + db.raw.namespaces[name].factions = {} + end + local id = faction + if type(db.raw.namespaces[name].factions[id]) ~= "table" then + db.raw.namespaces[name].factions[id] = {} + end + rawset(namespace, 'faction', db.raw.namespaces[name].factions[id]) + if namespace.defaults and namespace.defaults.faction then + inheritDefaults(namespace.faction, namespace.defaults.faction) + end + return namespace.faction + elseif key == "class" then + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].classes) ~= "table" then + db.raw.namespaces[name].classes = {} + end + local id = classID + if type(db.raw.namespaces[name].classes[id]) ~= "table" then + db.raw.namespaces[name].classes[id] = {} + end + rawset(namespace, 'class', db.raw.namespaces[name].classes[id]) + if namespace.defaults and namespace.defaults.class then + inheritDefaults(namespace.class, namespace.defaults.class) + end + return namespace.class + elseif key == "profile" then + if type(db.raw.namespaces) ~= "table" then + db.raw.namespaces = {} + end + if type(db.raw.namespaces[name]) ~= "table" then + db.raw.namespaces[name] = {} + end + if type(db.raw.namespaces[name].profiles) ~= "table" then + db.raw.namespaces[name].profiles = setmetatable({}, caseInsensitive_mt) + else + setmetatable(db.raw.namespaces[name].profiles, caseInsensitive_mt) + end + local id = db.raw.currentProfile[charID] + if id == "char" then + id = "char/" .. charID + elseif id == "class" then + id = "class/" .. classID + elseif id == "realm" then + id = "realm/" .. realmID + end + if type(db.raw.namespaces[name].profiles[id]) ~= "table" then + db.raw.namespaces[name].profiles[id] = {} + end + rawset(namespace, 'profile', db.raw.namespaces[name].profiles[id]) + if namespace.defaults and namespace.defaults.profile then + inheritDefaults(namespace.profile, namespace.defaults.profile) + end + return namespace.profile + elseif key == "defaults" or key == "name" or key == "db" then + return nil + end + error(("Cannot access key %q in db table. You may want to use db.profile[%q]"):format(tostring(key), tostring(key)), 2) +end, __newindex = function(db, key, value) + error(("Cannot access key %q in db table. You may want to use db.profile[%q]"):format(tostring(key), tostring(key)), 2) +end } + +local tmp = {} +function AceDB:InitializeDB(addonName) + local db = self.db + + if not db then + if addonName then + AceDB.addonsLoaded[addonName] = true + end + return + end + + if db.raw then + -- someone manually initialized + return + end + + if type(_G[db.name]) ~= "table" then + _G[db.name] = {} + else + CrawlForDeserialization(_G[db.name]) + end + if type(_G[db.charName]) == "table" then + CrawlForDeserialization(_G[db.charName]) + end + rawset(db, 'raw', _G[db.name]) + if not db.raw.currentProfile then + db.raw.currentProfile = {} + else + for k,v in pairs(db.raw.currentProfile) do + tmp[convertFromOldCharID(k)] = v + db.raw.currentProfile[k] = nil + end + for k,v in pairs(tmp) do + db.raw.currentProfile[k] = v + tmp[k] = nil + end + end + if not db.raw.currentProfile[charID] then + db.raw.currentProfile[charID] = "Default" + end + if db.raw.profiles then + for k,v in pairs(db.raw.profiles) do + local new_k = k + if k:find("^char/") then + new_k = "char/" .. convertFromOldCharID(k:sub(6)) + end + tmp[new_k] = v + db.raw.profiles[k] = nil + end + for k,v in pairs(tmp) do + db.raw.profiles[k] = v + tmp[k] = nil + end + end + if db.raw.disabledModules then -- AceModuleCore-2.0 + for k,v in pairs(db.raw.disabledModules) do + local new_k = k + if k:find("^char/") then + new_k = "char/" .. convertFromOldCharID(k:sub(6)) + end + tmp[new_k] = v + db.raw.disabledModules[k] = nil + end + for k,v in pairs(tmp) do + db.raw.disabledModules[k] = v + tmp[k] = nil + end + end + if db.raw.chars then + for k,v in pairs(db.raw.chars) do + tmp[convertFromOldCharID(k)] = v + db.raw.chars[k] = nil + end + for k,v in pairs(tmp) do + db.raw.chars[k] = v + tmp[k] = nil + end + end + if db.raw.namespaces then + for l,u in pairs(db.raw.namespaces) do + if u.chars then + for k,v in pairs(u.chars) do + tmp[convertFromOldCharID(k)] = v + u.chars[k] = nil + end + for k,v in pairs(tmp) do + u.chars[k] = v + tmp[k] = nil + end + end + end + end + if db.raw.disabled then + setmetatable(db.raw.disabled, caseInsensitive_mt) + end + if self['acedb-profile-copylist'] then + RecalculateAceDBCopyFromList(self) + end + if self['acedb-profile-list'] then + RecalculateAceDBProfileList(self) + end + setmetatable(db, db_mt) +end + +function AceDB:OnEmbedInitialize(target, name) + if name then + self:ADDON_LOADED(name) + end + self.InitializeDB(target, name) +end + +function AceDB:RegisterDB(name, charName) + AceDB:argCheck(name, 2, "string") + AceDB:argCheck(charName, 3, "string", "nil") + if self.db then + AceDB:error("Cannot call \"RegisterDB\" if self.db is set.") + end + local stack = debugstack() + local addonName = stack:gsub(".-\n.-\\AddOns\\(.-)\\.*", "%1") + self.db = { + name = name, + charName = charName + } + if AceDB.addonsLoaded[addonName] then + AceDB.InitializeDB(self, addonName) + else + AceDB.addonsToBeInitialized[self] = addonName + end + AceDB.registry[self] = true +end + +function AceDB:RegisterDefaults(kind, defaults, a3) + local name + if a3 then + name, kind, defaults = kind, defaults, a3 + AceDB:argCheck(name, 2, "string") + AceDB:argCheck(kind, 3, "string") + AceDB:argCheck(defaults, 4, "table") + else + AceDB:argCheck(kind, 2, "string") + AceDB:argCheck(defaults, 3, "table") + end + if kind ~= "char" and kind ~= "class" and kind ~= "profile" and kind ~= "account" and kind ~= "realm" and kind ~= "faction" and kind ~= "server" then + AceDB:error("Bad argument #%d to `RegisterDefaults' (\"char\", \"class\", \"profile\", \"account\", \"realm\", \"server\", or \"faction\" expected, got %q)", a3 and 3 or 2, kind) + end + if type(self.db) ~= "table" or type(self.db.name) ~= "string" then + AceDB:error("Cannot call \"RegisterDefaults\" unless \"RegisterDB\" has been previously called.") + end + local db + if name then + local namespace = self:AcquireDBNamespace(name) + if namespace.defaults and namespace.defaults[kind] then + AceDB:error("\"RegisterDefaults\" has already been called for %q::%q.", name, kind) + end + db = namespace + else + if self.db.defaults and self.db.defaults[kind] then + AceDB:error("\"RegisterDefaults\" has already been called for %q.", kind) + end + db = self.db + end + if not db.defaults then + rawset(db, 'defaults', {}) + end + db.defaults[kind] = defaults + if rawget(db, kind) then + inheritDefaults(db[kind], defaults) + end +end + +function AceDB:ResetDB(kind, a2) + local name + if a2 then + name, kind = kind, a2 + AceDB:argCheck(name, 2, "nil", "string") + AceDB:argCheck(kind, 3, "nil", "string") + else + AceDB:argCheck(kind, 2, "nil", "string") + if kind ~= "char" and kind ~= "class" and kind ~= "profile" and kind ~= "account" and kind ~= "realm" and kind ~= "faction" and kind ~= "server" then + name, kind = kind, nil + end + end + if not self.db or not self.db.raw then + AceDB:error("Cannot call \"ResetDB\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.") + end + local db = self.db + if not kind then + if not name then + if db.charName then + _G[db.charName] = nil + end + _G[db.name] = nil + rawset(db, 'raw', nil) + AceDB.InitializeDB(self) + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'account', nil) + rawset(v, 'char', nil) + rawset(v, 'class', nil) + rawset(v, 'profile', nil) + rawset(v, 'realm', nil) + rawset(v, 'server', nil) + rawset(v, 'faction', nil) + end + end + else + if db.raw.namespaces then + db.raw.namespaces[name] = nil + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'account', nil) + rawset(v, 'char', nil) + rawset(v, 'class', nil) + rawset(v, 'profile', nil) + rawset(v, 'realm', nil) + rawset(v, 'server', nil) + rawset(v, 'faction', nil) + end + end + end + elseif kind == "account" then + if name then + db.raw.account = nil + rawset(db, 'account', nil) + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + v.account = nil + end + end + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'account', nil) + end + end + else + if db.raw.namespaces and db.raw.namespaces[name] then + db.raw.namespaces[name].account = nil + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'account', nil) + end + end + end + elseif kind == "char" then + if name then + if db.charName then + _G[db.charName] = nil + else + if db.raw.chars then + db.raw.chars[charID] = nil + end + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + if v.chars then + v.chars[charID] = nil + end + end + end + end + rawset(db, 'char', nil) + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'char', nil) + end + end + else + if db.charName then + local x = _G[db.charName] + if x.namespaces then + x.namespaces[name] = nil + end + else + if db.raw.namespaces then + local v = db.namespaces[name] + if v and v.chars then + v.chars[charID] = nil + end + end + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'char', nil) + end + end + end + elseif kind == "realm" then + if not name then + if db.raw.realms then + db.raw.realms[realmID] = nil + end + rawset(db, 'realm', nil) + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + if v.realms then + v.realms[realmID] = nil + end + end + end + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'realm', nil) + end + end + else + if db.raw.namespaces then + local v = db.raw.namespaces[name] + if v and v.realms then + v.realms[realmID] = nil + end + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'realm', nil) + end + end + end + elseif kind == "server" then + if not name then + if db.raw.servers then + db.raw.servers[server] = nil + end + rawset(db, 'server', nil) + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + if v.servers then + v.servers[server] = nil + end + end + end + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'server', nil) + end + end + else + if db.raw.namespaces then + local v = db.raw.namespaces[name] + if v and v.servers then + v.servers[server] = nil + end + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'server', nil) + end + end + end + elseif kind == "faction" then + if not name then + if db.raw.factions then + db.raw.factions[faction] = nil + end + rawset(db, 'faction', nil) + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + if v.factions then + v.factions[faction] = nil + end + end + end + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'faction', nil) + end + end + else + if db.raw.namespaces then + local v = db.raw.namespaces[name] + if v and v.factions then + v.factions[faction] = nil + end + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'faction', nil) + end + end + end + elseif kind == "class" then + if not name then + if db.raw.realms then + db.raw.realms[classID] = nil + end + rawset(db, 'class', nil) + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + if v.classes then + v.classes[classID] = nil + end + end + end + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'class', nil) + end + end + else + if db.raw.namespaces then + local v = db.raw.namespaces[name] + if v and v.classes then + v.classes[classID] = nil + end + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'class', nil) + end + end + end + elseif kind == "profile" then + local id = db.raw.currentProfile and db.raw.currentProfile[charID] or "Default" + if id == "char" then + id = "char/" .. charID + elseif id == "class" then + id = "class/" .. classID + elseif id == "realm" then + id = "realm/" .. realmID + end + if not name then + if db.raw.profiles then + db.raw.profiles[id] = nil + end + rawset(db, 'profile', nil) + if db.raw.namespaces then + for name,v in pairs(db.raw.namespaces) do + if v.profiles then + v.profiles[id] = nil + end + end + end + if db.namespaces then + for name,v in pairs(db.namespaces) do + rawset(v, 'profile', nil) + end + end + else + if db.raw.namespaces then + local v = db.raw.namespaces[name] + if v and v.profiles then + v.profiles[id] = nil + end + end + if db.namespaces then + local v = db.namespaces[name] + if v then + rawset(v, 'profile', nil) + end + end + end + end +end + +local function cleanDefaults(t, defaults) + if defaults then + for k,v in pairs(defaults) do + if k == "*" then + if type(v) == "table" then + for k in pairs(t) do + if (defaults[k] == nil or k == "*") and type(t[k]) == "table" then + if cleanDefaults(t[k], v) then + t[k] = nil + end + end + end + else + for k in pairs(t) do + if (defaults[k] == nil or k == "*") and t[k] == v then + t[k] = nil + end + end + end + else + if type(v) == "table" then + if type(t[k]) == "table" then + if cleanDefaults(t[k], v) then + t[k] = nil + end + end + elseif t[k] == v then + t[k] = nil + end + end + end + end + return t and not next(t) +end + +function AceDB:GetProfile() + if not self.db or not self.db.raw then + return nil + end + if not self.db.raw.currentProfile then + self.db.raw.currentProfile = {} + end + if not self.db.raw.currentProfile[charID] then + self.db.raw.currentProfile[charID] = "Default" + end + local profile = self.db.raw.currentProfile[charID] + if profile == "char" then + return "char", "char/" .. charID + elseif profile == "class" then + return "class", "class/" .. classID + elseif profile == "realm" then + return "realm", "realm/" .. realmID + end + return profile, profile +end + +local function copyTable(to, from) + setmetatable(to, nil) + for k,v in pairs(from) do + if type(k) == "table" then + k = copyTable({}, k) + end + if type(v) == "table" then + v = copyTable({}, v) + end + to[k] = v + end + setmetatable(to, from) + return to +end + +function AceDB:SetProfile(name) + AceDB:argCheck(name, 2, "string") + if not self.db or not self.db.raw then + AceDB:error("Cannot call \"SetProfile\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.") + end + local db = self.db + local lowerName = name:lower() + if lowerName:find("^char/") or lowerName:find("^realm/") or lowerName:find("^class/") then + if lowerName:find("^char/") then + name = "char" + else + name = lowerName:sub(1, 5) + end + lowerName = name:lower() + end + local oldName = db.raw.currentProfile[charID] + if oldName:lower() == name:lower() then + return + end + local oldProfileData = db.profile + local realName = name + if lowerName == "char" then + realName = name .. "/" .. charID + elseif lowerName == "realm" then + realName = name .. "/" .. realmID + elseif lowerName == "class" then + realName = name .. "/" .. classID + end + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedProfileDisable) == "function" then + safecall(mixin.OnEmbedProfileDisable, mixin, self, realName) + end + end + end + current = current.super + end + if type(self.OnProfileDisable) == "function" then + safecall(self.OnProfileDisable, self, realName) + end + local active = self:IsActive() + db.raw.currentProfile[charID] = name + rawset(db, 'profile', nil) + if db.namespaces then + for k,v in pairs(db.namespaces) do + rawset(v, 'profile', nil) + end + end + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedProfileEnable) == "function" then + safecall(mixin.OnEmbedProfileEnable, mixin, self, oldName, oldProfileData) + end + end + end + current = current.super + end + if type(self.OnProfileEnable) == "function" then + safecall(self.OnProfileEnable, self, oldName, oldProfileData) + end + if cleanDefaults(oldProfileData, db.defaults and db.defaults.profile) then + db.raw.profiles[oldName] = nil + if not next(db.raw.profiles) then + db.raw.profiles = nil + end + end + local newactive = self:IsActive() + if active ~= newactive then + local first = nil + if AceOO.inherits(self, "AceAddon-2.0") then + local AceAddon = AceLibrary("AceAddon-2.0") + if not AceAddon.addonsStarted[self] then + return + end + if AceAddon.addonsEnabled and not AceAddon.addonsEnabled[self] then + first = true + end + end + if newactive then + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable, mixin, self, first) + end + end + end + current = current.super + end + if type(self.OnEnable) == "function" then + safecall(self.OnEnable, self, first) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", self, first) + end + else + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedDisable) == "function" then + safecall(mixin.OnEmbedDisable, mixin, self) + end + end + end + current = current.super + end + if type(self.OnDisable) == "function" then + safecall(self.OnDisable, self) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonDisabled", self) + end + end + end + if self['acedb-profile-list'] then + RecalculateAceDBProfileList(self) + end + if self['acedb-profile-copylist'] then + RecalculateAceDBCopyFromList(self) + end + if Dewdrop then + Dewdrop:Refresh(1) + Dewdrop:Refresh(2) + Dewdrop:Refresh(3) + Dewdrop:Refresh(4) + Dewdrop:Refresh(5) + end +end + +function AceDB:CopyProfileFrom(copyFrom) + AceDB:argCheck(copyFrom, 2, "string") + if not self.db or not self.db.raw then + AceDB:error("Cannot call \"CopyProfileFrom\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.") + end + local db = self.db + local lowerCopyFrom = copyFrom:lower() + if not db.raw.profiles or not db.raw.profiles[copyFrom] then + local good = false + if db.raw.namespaces then + for _,n in pairs(db.raw.namespaces) do + if n.profiles and n.profiles[copyFrom] then + good = true + break + end + end + end + if not good then + AceDB:error("Cannot copy from profile %q, it does not exist.", copyFrom) + end + end + local currentProfile = db.raw.currentProfile[charID] + if currentProfile:lower() == lowerCopyFrom then + AceDB:error("Cannot copy from profile %q, it is currently in use.", copyFrom) + end + local oldProfileData = db.profile + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedProfileDisable) == "function" then + safecall(mixin.OnEmbedProfileDisable, mixin, self, currentProfile) + end + end + end + current = current.super + end + if type(self.OnProfileDisable) == "function" then + safecall(self.OnProfileDisable, self, realName) + end + local active = self:IsActive() + for k,v in pairs(db.profile) do + db.profile[k] = nil + end + if db.raw.profiles[copyFrom] then + copyTable(db.profile, db.raw.profiles[copyFrom]) + end + inheritDefaults(db.profile, db.defaults and db.defaults.profile) + if db.namespaces then + for l,u in pairs(db.namespaces) do + for k,v in pairs(u.profile) do + u.profile[k] = nil + end + if db.raw.namespaces[l].profiles[copyFrom] then + copyTable(u.profile, db.raw.namespaces[l].profiles[copyFrom]) + end + inheritDefaults(u.profile, u.defaults and u.defaults.profile) + end + end + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedProfileEnable) == "function" then + safecall(mixin.OnEmbedProfileEnable, mixin, self, copyFrom, oldProfileData, copyFrom) + end + end + end + current = current.super + end + if type(self.OnProfileEnable) == "function" then + safecall(self.OnProfileEnable, self, copyFrom, oldProfileData, copyFrom) + end + local newactive = self:IsActive() + if active ~= newactive then + if AceOO.inherits(self, "AceAddon-2.0") then + local AceAddon = AceLibrary("AceAddon-2.0") + if not AceAddon.addonsStarted[self] then + return + end + end + if newactive then + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable, mixin, self) + end + end + end + current = current.super + end + if type(self.OnEnable) == "function" then + safecall(self.OnEnable, self) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", self) + end + else + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedDisable) == "function" then + safecall(mixin.OnEmbedDisable, mixin, self) + end + end + end + current = current.super + end + if type(self.OnDisable) == "function" then + safecall(self.OnDisable, self) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonDisabled", self) + end + end + end + if self['acedb-profile-list'] then + RecalculateAceDBProfileList(self) + end + if self['acedb-profile-copylist'] then + RecalculateAceDBCopyFromList(self) + end + if Dewdrop then + Dewdrop:Refresh(1) + Dewdrop:Refresh(2) + Dewdrop:Refresh(3) + Dewdrop:Refresh(4) + Dewdrop:Refresh(5) + end +end + +function AceDB:IsActive() + return not self.db or not self.db.raw or not self.db.raw.disabled or not self.db.raw.disabled[self.db.raw.currentProfile[charID]] +end + +function AceDB:ToggleActive(state) + AceDB:argCheck(state, 2, "boolean", "nil") + if not self.db or not self.db.raw then + AceDB:error("Cannot call \"ToggleActive\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.") + end + local db = self.db + if not db.raw.disabled then + db.raw.disabled = setmetatable({}, caseInsensitive_mt) + end + local profile = db.raw.currentProfile[charID] + local disable + if state == nil then + disable = not db.raw.disabled[profile] + else + disable = not state + if disable == db.raw.disabled[profile] then + return + end + end + db.raw.disabled[profile] = disable or nil + if AceOO.inherits(self, "AceAddon-2.0") then + local AceAddon = AceLibrary("AceAddon-2.0") + if not AceAddon.addonsStarted[self] then + return + end + end + if not disable then + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedEnable) == "function" then + safecall(mixin.OnEmbedEnable, mixin, self) + end + end + end + current = current.super + end + if type(self.OnEnable) == "function" then + safecall(self.OnEnable, self) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonEnabled", self) + end + else + local current = self.class + while current and current ~= AceOO.Class do + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnEmbedDisable) == "function" then + safecall(mixin.OnEmbedDisable, mixin, self) + end + end + end + current = current.super + end + if type(self.OnDisable) == "function" then + safecall(self.OnDisable, self) + end + if AceEvent then + AceEvent:TriggerEvent("Ace2_AddonDisabled", self) + end + end + return not disable +end + +function AceDB:embed(target) + self.super.embed(self, target) + if not AceEvent then + AceDB:error(MAJOR_VERSION .. " requires AceEvent-2.0") + end +end + +function AceDB:ADDON_LOADED(name) + AceDB.addonsLoaded[name] = true + for addon, addonName in pairs(AceDB.addonsToBeInitialized) do + if name == addonName then + AceDB.InitializeDB(addon, name) + AceDB.addonsToBeInitialized[addon] = nil + end + end +end + +function AceDB:PLAYER_LOGOUT() + for addon in pairs(AceDB.registry) do + local db = addon.db + if db then + setmetatable(db, nil) + CrawlForSerialization(db.raw) + if type(_G[db.charName]) == "table" then + CrawlForSerialization(_G[db.charName]) + end + if db.char and cleanDefaults(db.char, db.defaults and db.defaults.char) then + if db.charName and _G[db.charName] and _G[db.charName].global == db.char then + _G[db.charName].global = nil + if not next(_G[db.charName]) then + _G[db.charName] = nil + end + else + if db.raw.chars then + db.raw.chars[charID] = nil + if not next(db.raw.chars) then + db.raw.chars = nil + end + end + end + end + if db.realm and cleanDefaults(db.realm, db.defaults and db.defaults.realm) then + if db.raw.realms then + db.raw.realms[realmID] = nil + if not next(db.raw.realms) then + db.raw.realms = nil + end + end + end + if db.server and cleanDefaults(db.server, db.defaults and db.defaults.server) then + if db.raw.servers then + db.raw.servers[server] = nil + if not next(db.raw.servers) then + db.raw.servers = nil + end + end + end + if db.faction and cleanDefaults(db.faction, db.defaults and db.defaults.faction) then + if db.raw.factions then + db.raw.factions[faction] = nil + if not next(db.raw.factions) then + db.raw.factions = nil + end + end + end + if db.class and cleanDefaults(db.class, db.defaults and db.defaults.class) then + if db.raw.classes then + db.raw.classes[classID] = nil + if not next(db.raw.classes) then + db.raw.classes = nil + end + end + end + if db.account and cleanDefaults(db.account, db.defaults and db.defaults.account) then + db.raw.account = nil + end + if db.profile and cleanDefaults(db.profile, db.defaults and db.defaults.profile) then + if db.raw.profiles then + db.raw.profiles[db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"] = nil + if not next(db.raw.profiles) then + db.raw.profiles = nil + end + end + end + if db.namespaces and db.raw.namespaces then + for name,v in pairs(db.namespaces) do + if db.raw.namespaces[name] then + setmetatable(v, nil) + if v.char and cleanDefaults(v.char, v.defaults and v.defaults.char) then + if db.charName and _G[db.charName] and _G[db.charName].namespaces and _G[db.charName].namespaces[name] == v then + _G[db.charName].namespaces[name] = nil + if not next(_G[db.charName].namespaces) then + _G[db.charName].namespaces = nil + if not next(_G[db.charName]) then + _G[db.charName] = nil + end + end + else + if db.raw.namespaces[name].chars then + db.raw.namespaces[name].chars[charID] = nil + if not next(db.raw.namespaces[name].chars) then + db.raw.namespaces[name].chars = nil + end + end + end + end + if v.realm and cleanDefaults(v.realm, v.defaults and v.defaults.realm) then + if db.raw.namespaces[name].realms then + db.raw.namespaces[name].realms[realmID] = nil + if not next(db.raw.namespaces[name].realms) then + db.raw.namespaces[name].realms = nil + end + end + end + if v.server and cleanDefaults(v.server, v.defaults and v.defaults.server) then + if db.raw.namespaces[name].servers then + db.raw.namespaces[name].servers[server] = nil + if not next(db.raw.namespaces[name].servers) then + db.raw.namespaces[name].servers = nil + end + end + end + if v.faction and cleanDefaults(v.faction, v.defaults and v.defaults.faction) then + if db.raw.namespaces[name].factions then + db.raw.namespaces[name].factions[faction] = nil + if not next(db.raw.namespaces[name].factions) then + db.raw.namespaces[name].factions = nil + end + end + end + if v.class and cleanDefaults(v.class, v.defaults and v.defaults.class) then + if db.raw.namespaces[name].classes then + db.raw.namespaces[name].classes[classID] = nil + if not next(db.raw.namespaces[name].classes) then + db.raw.namespaces[name].classes = nil + end + end + end + if v.account and cleanDefaults(v.account, v.defaults and v.defaults.account) then + db.raw.namespaces[name].account = nil + end + if v.profile and cleanDefaults(v.profile, v.defaults and v.defaults.profile) then + if db.raw.namespaces[name].profiles then + db.raw.namespaces[name].profiles[db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"] = nil + if not next(db.raw.namespaces[name].profiles) then + db.raw.namespaces[name].profiles = nil + end + end + end + if not next(db.raw.namespaces[name]) then + db.raw.namespaces[name] = nil + end + end + end + if not next(db.raw.namespaces) then + db.raw.namespaces = nil + end + end + if db.raw.disabled and not next(db.raw.disabled) then + db.raw.disabled = nil + end + if db.raw.currentProfile then + for k,v in pairs(db.raw.currentProfile) do + if v:lower() == "default" then + db.raw.currentProfile[k] = nil + end + end + if not next(db.raw.currentProfile) then + db.raw.currentProfile = nil + end + end + if _G[db.name] and not next(_G[db.name]) then + _G[db.name] = nil + end + end + end +end + +function AceDB:AcquireDBNamespace(name) + AceDB:argCheck(name, 2, "string") + local db = self.db + if not db then + AceDB:error("Cannot call `AcquireDBNamespace' before `RegisterDB' has been called.", 2) + end + if not db.namespaces then + rawset(db, 'namespaces', {}) + end + if not db.namespaces[name] then + local namespace = {} + db.namespaces[name] = namespace + namespace.db = db + namespace.name = name + setmetatable(namespace, namespace_mt) + end + return db.namespaces[name] +end + +function AceDB:GetAceOptionsDataTable(target) + if not target['acedb-profile-list'] then + target['acedb-profile-list'] = setmetatable({}, caseInsensitive_mt) + RecalculateAceDBProfileList(target) + end + if not target['acedb-profile-copylist'] then + target['acedb-profile-copylist'] = setmetatable({}, caseInsensitive_mt) + RecalculateAceDBCopyFromList(target) + end + return { + standby = { + cmdName = STATE, + guiName = ENABLED, + name = ACTIVE, + desc = TOGGLE_ACTIVE, + type = "toggle", + get = "IsActive", + set = "ToggleActive", + map = MAP_ACTIVESUSPENDED, + order = -3, + }, + profile = { + type = 'group', + name = PROFILE, + desc = SET_PROFILE, + order = -3.5, + get = "GetProfile", + args = { + choose = { + guiName = CHOOSE_PROFILE_GUI, + cmdName = PROFILE, + desc = CHOOSE_PROFILE_DESC, + type = 'text', + get = "GetProfile", + set = "SetProfile", + validate = target['acedb-profile-list'] + }, + copy = { + guiName = COPY_PROFILE_GUI, + cmdName = PROFILE, + desc = COPY_PROFILE_DESC, + type = 'text', + get = false, + set = "CopyProfileFrom", + validate = target['acedb-profile-copylist'], + disabled = function() + return not next(target['acedb-profile-copylist']) + end, + }, + other = { + guiName = OTHER_PROFILE_GUI, + cmdName = PROFILE, + desc = OTHER_PROFILE_DESC, + usage = OTHER_PROFILE_USAGE, + type = 'text', + get = "GetProfile", + set = "SetProfile", + } + } + }, + } +end + +local function activate(self, oldLib, oldDeactivate) + AceDB = self + AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0") + + self.addonsToBeInitialized = oldLib and oldLib.addonsToBeInitialized or {} + self.addonsLoaded = oldLib and oldLib.addonsLoaded or {} + self.registry = oldLib and oldLib.registry or {} + + self:activate(oldLib, oldDeactivate) + + for t in pairs(self.embedList) do + if t.db then + rawset(t.db, 'char', nil) + rawset(t.db, 'realm', nil) + rawset(t.db, 'class', nil) + rawset(t.db, 'account', nil) + rawset(t.db, 'server', nil) + rawset(t.db, 'faction', nil) + rawset(t.db, 'profile', nil) + setmetatable(t.db, db_mt) + end + end + + if oldLib then + oldDeactivate(oldLib) + end +end + +local function external(self, major, instance) + if major == "AceEvent-2.0" then + AceEvent = instance + + AceEvent:embed(self) + + self:RegisterEvent("ADDON_LOADED") + self:RegisterEvent("PLAYER_LOGOUT") + elseif major == "Dewdrop-2.0" then + Dewdrop = instance + end +end + +AceLibrary:Register(AceDB, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) +AceDB = AceLibrary(MAJOR_VERSION) diff --git a/libs/AceEvent-2.0/AceEvent-2.0.lua b/libs/AceEvent-2.0/AceEvent-2.0.lua new file mode 100644 index 0000000..027dc09 --- /dev/null +++ b/libs/AceEvent-2.0/AceEvent-2.0.lua @@ -0,0 +1,1040 @@ +--[[ +Name: AceEvent-2.0 +Revision: $Rev: 23383 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceEvent-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0 +Description: Mixin to allow for event handling, scheduling, and inter-addon + communication. +Dependencies: AceLibrary, AceOO-2.0 +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "AceEvent-2.0" +local MINOR_VERSION = "$Revision: 23383 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end + +local AceOO = AceLibrary:GetInstance("AceOO-2.0") +local Mixin = AceOO.Mixin +local AceEvent = Mixin { + "RegisterEvent", + "RegisterAllEvents", + "UnregisterEvent", + "UnregisterAllEvents", + "TriggerEvent", + "ScheduleEvent", + "ScheduleRepeatingEvent", + "CancelScheduledEvent", + "CancelAllScheduledEvents", + "IsEventRegistered", + "IsEventScheduled", + "RegisterBucketEvent", + "UnregisterBucketEvent", + "UnregisterAllBucketEvents", + "IsBucketEventRegistered", + "ScheduleLeaveCombatAction", + "CancelAllCombatSchedules", + } + +local weakKey = {__mode="k"} + +local FAKE_NIL +local RATE + +local eventsWhichHappenOnce = { + PLAYER_LOGIN = true, + AceEvent_FullyInitialized = true, + VARIABLES_LOADED = true, + PLAYER_LOGOUT = true, +} +local next = next +local pairs = pairs +local pcall = pcall +local type = type +local GetTime = GetTime +local gcinfo = gcinfo +local unpack = unpack +local geterrorhandler = geterrorhandler + +local new, del +do + local cache = setmetatable({}, {__mode='k'}) + function new(...) + local t = next(cache) + if t then + cache[t] = nil + for i = 1, select('#', ...) do + t[i] = select(i, ...) + end + return t + else + return { ... } + end + end + function del(t) + for k in pairs(t) do + t[k] = nil + end + cache[t] = true + return nil + end +end + +local registeringFromAceEvent +function AceEvent:RegisterEvent(event, method, once) + AceEvent:argCheck(event, 2, "string") + if self == AceEvent and not registeringFromAceEvent then + AceEvent:argCheck(method, 3, "function") + self = method + else + AceEvent:argCheck(method, 3, "string", "function", "nil", "boolean", "number") + if type(method) == "boolean" or type(method) == "number" then + AceEvent:argCheck(once, 4, "nil") + once, method = method, event + end + end + AceEvent:argCheck(once, 4, "number", "boolean", "nil") + if eventsWhichHappenOnce[event] then + once = true + end + local throttleRate + if type(once) == "number" then + throttleRate, once = once + end + if not method then + method = event + end + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) + else + assert(type(method) == "function" or type(method) == "string") + end + + local AceEvent_registry = AceEvent.registry + if not AceEvent_registry[event] then + AceEvent_registry[event] = new() + AceEvent.frame:RegisterEvent(event) + end + + local remember = true + if AceEvent_registry[event][self] then + remember = false + end + AceEvent_registry[event][self] = method + + local AceEvent_onceRegistry = AceEvent.onceRegistry + if once then + if not AceEvent_onceRegistry then + AceEvent.onceRegistry = {} + AceEvent_onceRegistry = AceEvent.onceRegistry + end + if not AceEvent_onceRegistry[event] then + AceEvent_onceRegistry[event] = new() + end + AceEvent_onceRegistry[event][self] = true + else + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then + AceEvent_onceRegistry[event][self] = nil + if not next(AceEvent_onceRegistry[event]) then + AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) + end + end + end + + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + if throttleRate then + if not AceEvent_throttleRegistry then + AceEvent.throttleRegistry = {} + AceEvent_throttleRegistry = AceEvent.throttleRegistry + end + if not AceEvent_throttleRegistry[event] then + AceEvent_throttleRegistry[event] = new() + end + if AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = nil + end + AceEvent_throttleRegistry[event][self] = setmetatable(new(), weakKey) + local t = AceEvent_throttleRegistry[event][self] + t[RATE] = throttleRate + else + if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] then + if AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = nil + end + if not next(AceEvent_throttleRegistry[event]) then + AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) + end + end + end + + if remember then + AceEvent:TriggerEvent("AceEvent_EventRegistered", self, event) + end +end + +local ALL_EVENTS + +function AceEvent:RegisterAllEvents(method) + if self == AceEvent then + AceEvent:argCheck(method, 1, "function") + self = method + else + AceEvent:argCheck(method, 1, "string", "function") + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register all events to method %q, it does not exist", method) + end + end + + local AceEvent_registry = AceEvent.registry + if not AceEvent_registry[ALL_EVENTS] then + AceEvent_registry[ALL_EVENTS] = new() + AceEvent.frame:RegisterAllEvents() + end + + AceEvent_registry[ALL_EVENTS][self] = method +end + +local memstack, timestack = {}, {} +local memdiff, timediff + +function AceEvent:TriggerEvent(event, ...) + local tmp = new() + if type(event) ~= "string" then + DEFAULT_CHAT_FRAME:AddMessage(debugstack()) + end + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if (not AceEvent_registry[event] or not next(AceEvent_registry[event])) and (not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS])) then + return + end + local lastEvent = AceEvent.currentEvent + AceEvent.currentEvent = event + + local AceEvent_onceRegistry = AceEvent.onceRegistry + local AceEvent_debugTable = AceEvent.debugTable + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then + for obj, method in pairs(AceEvent_onceRegistry[event]) do + tmp[obj] = AceEvent_registry[event] and AceEvent_registry[event][obj] or nil + end + local obj = next(tmp) + while obj do + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = {} + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = { + mem = 0, + time = 0, + count = 0, + } + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + local method = tmp[obj] + AceEvent.UnregisterEvent(obj, event) + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + local success, err = pcall(obj_method, obj, ...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + elseif method then -- function + local success, err = pcall(method, ...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + tmp[obj] = nil + obj = next(tmp) + end + end + + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + local throttleTable = AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] + if AceEvent_registry[event] then + for obj, method in pairs(AceEvent_registry[event]) do + tmp[obj] = method + end + local obj = next(tmp) + while obj do + local method = tmp[obj] + local continue = false + if throttleTable and throttleTable[obj] then + local a1 = ... + if a1 == nil then + a1 = FAKE_NIL + end + if not throttleTable[obj][a1] or GetTime() - throttleTable[obj][a1] >= throttleTable[obj][RATE] then + throttleTable[obj][a1] = GetTime() + else + continue = true + end + end + if not continue then + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = {} + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = { + mem = 0, + time = 0, + count = 0, + } + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + local success, err = pcall(obj_method, obj, ...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + elseif method then -- function + local success, err = pcall(method, ...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + end + tmp[obj] = nil + obj = next(tmp) + end + end + if AceEvent_registry[ALL_EVENTS] then + for obj, method in pairs(AceEvent_registry[ALL_EVENTS]) do + tmp[obj] = method + end + local obj = next(tmp) + while obj do + local method = tmp[obj] + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = {} + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = {} + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + local success, err = pcall(obj_method, obj, ...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + elseif method then -- function + local success, err = pcall(method, ...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + tmp[obj] = nil + obj = next(tmp) + end + end + tmp = del(tmp) + AceEvent.currentEvent = lastEvent +end + +local delayRegistry +local tmp = {} +local function OnUpdate() + local t = GetTime() + for k,v in pairs(delayRegistry) do + tmp[k] = true + end + local AceEvent_debugTable = AceEvent.debugTable + for k in pairs(tmp) do + local v = delayRegistry[k] + if v then + local v_time = v.time + if not v_time then + delayRegistry[k] = nil + elseif v_time <= t then + local v_repeatDelay = v.repeatDelay + if v_repeatDelay then + -- use the event time, not the current time, else timing inaccuracies add up over time + v.time = v_time + v_repeatDelay + end + local event = v.event + local mem, time + if AceEvent_debugTable then + mem, time = gcinfo(), GetTime() + end + if type(event) == "function" then + local success, err = pcall(event, unpack(v, 1, v.n)) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + else + AceEvent:TriggerEvent(event, unpack(v, 1, v.n)) + end + if AceEvent_debugTable then + mem, time = gcinfo() - mem, GetTime() - time + v.mem = v.mem + mem + v.timeSpent = v.timeSpent + time + v.count = v.count + 1 + end + if not v_repeatDelay then + local x = delayRegistry[k] + if x and x.time == v_time then -- check if it was manually reset + if type(k) == "string" then + del(delayRegistry[k]) + end + delayRegistry[k] = nil + end + end + end + end + end + for k in pairs(tmp) do + tmp[k] = nil + end + if not next(delayRegistry) then + AceEvent.frame:Hide() + end +end + +local function ScheduleEvent(self, repeating, event, delay, ...) + local id + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + id, event, delay = event, delay, ... + AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(delay, 4, "number") + self:CancelScheduledEvent(id) + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + if not delayRegistry then + AceEvent.delayRegistry = {} + delayRegistry = AceEvent.delayRegistry + AceEvent.frame:SetScript("OnUpdate", OnUpdate) + end + local t + if type(id) == "table" then + for k in pairs(id) do + id[k] = nil + end + t = id + for i = 2, select('#', ...) do + t[i-1] = select(i, ...) + end + t.n = select('#', ...) - 1 + elseif id then + t = new(select(2, ...)) + t.n = select('#', ...) - 1 + else + t = { n = select('#', ...), ... } + end + t.event = event + t.time = GetTime() + delay + t.self = self + t.id = id or t + t.repeatDelay = repeating and delay + if AceEvent.debugTable then + t.mem = 0 + t.count = 0 + t.timeSpent = 0 + end + delayRegistry[t.id] = t + AceEvent.frame:Show() + return t.id +end + +function AceEvent:ScheduleEvent(event, delay, ...) + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(..., 4, "number") + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + return ScheduleEvent(self, false, event, delay, ...) +end + +function AceEvent:ScheduleRepeatingEvent(event, delay, ...) + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(..., 4, "number") + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + return ScheduleEvent(self, true, event, delay, ...) +end + +function AceEvent:CancelScheduledEvent(t) + AceEvent:argCheck(t, 2, "string", "table") + if delayRegistry then + local v = delayRegistry[t] + if v then + if type(t) == "string" then + del(delayRegistry[t]) + end + delayRegistry[t] = nil + if not next(delayRegistry) then + AceEvent.frame:Hide() + end + return true + end + end + return false +end + +function AceEvent:IsEventScheduled(t) + AceEvent:argCheck(t, 2, "string", "table") + if delayRegistry then + local v = delayRegistry[t] + if v then + return true, v.time - GetTime() + end + end + return false, nil +end + +function AceEvent:UnregisterEvent(event) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if AceEvent_registry[event] and AceEvent_registry[event][self] then + AceEvent_registry[event][self] = nil + local AceEvent_onceRegistry = AceEvent.onceRegistry + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] and AceEvent_onceRegistry[event][self] then + AceEvent_onceRegistry[event][self] = nil + if not next(AceEvent_onceRegistry[event]) then + AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) + end + end + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] and AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = nil + if not next(AceEvent_throttleRegistry[event]) then + AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) + end + end + if not next(AceEvent_registry[event]) then + AceEvent_registry[event] = del(AceEvent_registry[event]) + if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then + AceEvent.frame:UnregisterEvent(event) + end + end + else + if self == AceEvent then + error(("Cannot unregister event %q. Improperly unregistering from AceEvent-2.0."):format(event), 2) + else + AceEvent:error("Cannot unregister event %q. %q is not registered with it.", event, self) + end + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) +end + +function AceEvent:UnregisterAllEvents() + local AceEvent_registry = AceEvent.registry + if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then + AceEvent_registry[ALL_EVENTS][self] = nil + if not next(AceEvent_registry[ALL_EVENTS]) then + AceEvent_registry[ALL_EVENTS] = del(AceEvent_registry[ALL_EVENTS]) + AceEvent.frame:UnregisterAllEvents() + for k,v in pairs(AceEvent_registry) do + AceEvent.frame:RegisterEvent(k) + end + end + end + if AceEvent_registry.AceEvent_EventUnregistered then + local event, data = "AceEvent_EventUnregistered", AceEvent_registry.AceEvent_EventUnregistered + local x = data[self] + data[self] = nil + if x then + if not next(data) then + if not AceEvent_registry[ALL_EVENTS] then + AceEvent.frame:UnregisterEvent(event) + end + AceEvent_registry[event] = del(AceEvent_registry[event]) + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) + end + end + for event, data in pairs(AceEvent_registry) do + local x = data[self] + data[self] = nil + if x and event ~= ALL_EVENTS then + if not next(data) then + if not AceEvent_registry[ALL_EVENTS] then + AceEvent.frame:UnregisterEvent(event) + end + AceEvent_registry[event] = del(AceEvent_registry[event]) + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) + end + end + if AceEvent.onceRegistry then + for event, data in pairs(AceEvent.onceRegistry) do + data[self] = nil + end + end +end + +function AceEvent:CancelAllScheduledEvents() + if delayRegistry then + for k,v in pairs(delayRegistry) do + if v.self == self then + if type(k) == "string" then + del(delayRegistry[k]) + end + delayRegistry[k] = nil + end + end + if not next(delayRegistry) then + AceEvent.frame:Hide() + end + end +end + +function AceEvent:IsEventRegistered(event) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if self == AceEvent then + return AceEvent_registry[event] and next(AceEvent_registry[event]) and true or false + end + if AceEvent_registry[event] and AceEvent_registry[event][self] then + return true, AceEvent_registry[event][self] + end + return false, nil +end + +local bucketfunc +function AceEvent:RegisterBucketEvent(event, delay, method) + AceEvent:argCheck(event, 2, "string", "table") + if type(event) == "table" then + for k,v in pairs(event) do + if type(k) ~= "number" then + AceEvent:error("All keys to argument #2 to `RegisterBucketEvent' must be numbers.") + elseif type(v) ~= "string" then + AceEvent:error("All values to argument #2 to `RegisterBucketEvent' must be strings.") + end + end + end + AceEvent:argCheck(delay, 3, "number") + if AceEvent == self then + AceEvent:argCheck(method, 4, "function") + self = method + else + if type(event) == "string" then + AceEvent:argCheck(method, 4, "string", "function", "nil") + if not method then + method = event + end + else + AceEvent:argCheck(method, 4, "string", "function") + end + + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) + end + end + if not AceEvent.buckets then + AceEvent.buckets = {} + end + if not AceEvent.buckets[event] then + AceEvent.buckets[event] = new() + end + if not AceEvent.buckets[event][self] then + local t = new() + t.current = new() + t.self = self + AceEvent.buckets[event][self] = t + else + AceEvent.CancelScheduledEvent(self, AceEvent.buckets[event][self].id) + end + local bucket = AceEvent.buckets[event][self] + bucket.method = method + + local func = function(arg1) + bucket.run = true + if arg1 then + bucket.current[arg1] = true + end + end + AceEvent.buckets[event][self].func = func + if type(event) == "string" then + AceEvent.RegisterEvent(self, event, func) + else + for _,v in ipairs(event) do + AceEvent.RegisterEvent(self, v, func) + end + end + if not bucketfunc then + bucketfunc = function(bucket) + local current = bucket.current + local method = bucket.method + local self = bucket.self + if bucket.run then + if type(method) == "string" then + self[method](self, current) + elseif method then -- function + method(current) + end + for k in pairs(current) do + current[k] = nil + k = nil + end + bucket.run = false + end + end + end + bucket.id = AceEvent.ScheduleRepeatingEvent(self, bucketfunc, delay, bucket) +end + +function AceEvent:IsBucketEventRegistered(event) + AceEvent:argCheck(event, 2, "string", "table") + return AceEvent.buckets and AceEvent.buckets[event] and AceEvent.buckets[event][self] +end + +function AceEvent:UnregisterBucketEvent(event) + AceEvent:argCheck(event, 2, "string", "table") + if not AceEvent.buckets or not AceEvent.buckets[event] or not AceEvent.buckets[event][self] then + AceEvent:error("Cannot unregister bucket event %q. %q is not registered with it.", event, self) + end + + local bucket = AceEvent.buckets[event][self] + + if type(event) == "string" then + AceEvent.UnregisterEvent(self, event) + else + for _,v in ipairs(event) do + AceEvent.UnregisterEvent(self, v) + end + end + AceEvent:CancelScheduledEvent(bucket.id) + + bucket.current = del(bucket.current) + AceEvent.buckets[event][self] = del(bucket) + if not next(AceEvent.buckets[event]) then + AceEvent.buckets[event] = del(AceEvent.buckets[event]) + end +end + +function AceEvent:UnregisterAllBucketEvents() + if not AceEvent.buckets or not next(AceEvent.buckets) then + return + end + for k,v in pairs(AceEvent.buckets) do + if v == self then + AceEvent.UnregisterBucketEvent(self, k) + k = nil + end + end +end + +local combatSchedules +function AceEvent:CancelAllCombatSchedules() + local i = 0 + while true do + i = i + 1 + if not combatSchedules[i] then + break + end + local v = combatSchedules[i] + if v.self == self then + v = del(v) + table.remove(combatSchedules, i) + i = i - 1 + end + end +end + +local inCombat = false +local tmp = {} + +function AceEvent:PLAYER_REGEN_DISABLED() + inCombat = true +end + +function AceEvent:PLAYER_REGEN_ENABLED() + inCombat = false + for i, v in ipairs(combatSchedules) do + tmp[i] = v + combatSchedules[i] = nil + end + for i, v in ipairs(tmp) do + local func = v.func + if func then + local success, err = pcall(func, unpack(v, 1, v.n)) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + else + local obj = v.self + local method = v.method + local obj_method = obj[method] + if obj_method then + local success, err = pcall(obj_method, obj, unpack(v, 1, v.n)) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + end + end + tmp[i] = del(v) + end +end + +function AceEvent:ScheduleLeaveCombatAction(method, ...) + if self == AceEvent then + AceEvent:argCheck(method, 2, "function") + self = func + else + AceEvent:argCheck(method, 2, "function", "string") + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot schedule a combat action to method %q, it does not exist", method) + end + end + + if not inCombat then + local success, err + if type(method) == "function" then + success, err = pcall(method, ...) + else + success, err = pcall(self[method], self, ...) + end + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end + return + end + local t = new(...) + t.n = select('#', ...) + if type(method) == "function" then + t.func = method + else + t.method = method + end + t.self = self + table.insert(combatSchedules, t) +end + +function AceEvent:OnEmbedDisable(target) + self.UnregisterAllEvents(target) + + self.CancelAllScheduledEvents(target) + + self.UnregisterAllBucketEvents(target) + + self.CancelAllCombatSchedules(target) +end + +function AceEvent:EnableDebugging() + if not self.debugTable then + self.debugTable = {} + + if delayRegistry then + for k,v in pairs(self.delayRegistry) do + if not v.mem then + v.mem = 0 + v.count = 0 + v.timeSpent = 0 + end + end + end + end +end + +function AceEvent:IsFullyInitialized() + return self.postInit or false +end + +function AceEvent:IsPostPlayerLogin() + return self.playerLogin or false +end + +local function activate(self, oldLib, oldDeactivate) + AceEvent = self + + self.onceRegistry = oldLib and oldLib.onceRegistry or {} + self.throttleRegistry = oldLib and oldLib.throttleRegistry or {} + self.delayRegistry = oldLib and oldLib.delayRegistry or {} + self.buckets = oldLib and oldLib.buckets or {} + self.registry = oldLib and oldLib.registry or {} + self.frame = oldLib and oldLib.frame or CreateFrame("Frame", "AceEvent20Frame") + self.debugTable = oldLib and oldLib.debugTable + self.playerLogin = oldLib and oldLib.pew or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage and true + self.postInit = oldLib and oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true + self.ALL_EVENTS = oldLib and oldLib.ALL_EVENTS or {} + self.FAKE_NIL = oldLib and oldLib.FAKE_NIL or {} + self.RATE = oldLib and oldLib.RATE or {} + self.combatSchedules = oldLib and oldLib.combatSchedules or {} + + combatSchedules = self.combatSchedules + ALL_EVENTS = self.ALL_EVENTS + FAKE_NIL = self.FAKE_NIL + RATE = self.RATE + local inPlw = false + local blacklist = { + UNIT_INVENTORY_CHANGED = true, + BAG_UPDATE = true, + ITEM_LOCK_CHANGED = true, + ACTIONBAR_SLOT_CHANGED = true, + } + self.frame:SetScript("OnEvent", function(_, event, ...) + if event == "PLAYER_ENTERING_WORLD" then + inPlw = false + elseif event == "PLAYER_LEAVING_WORLD" then + inPlw = true + end + if event and (not inPlw or not blacklist[event]) then + self:TriggerEvent(event, ...) + end + end) + if self.delayRegistry then + delayRegistry = self.delayRegistry + self.frame:SetScript("OnUpdate", OnUpdate) + end + + self:UnregisterAllEvents() + self:CancelAllScheduledEvents() + + registeringFromAceEvent = true + self:RegisterEvent("LOOT_OPENED", function() + SendAddonMessage("LOOT_OPENED", "", "RAID") + end) + registeringFromAceEvent = nil + + if not self.playerLogin then + registeringFromAceEvent = true + self:RegisterEvent("PLAYER_LOGIN", function() + collectgarbage('collect') + self.playerLogin = true + end, true) + registeringFromAceEvent = nil + end + + if not self.postInit then + local isReload = true + local function func() + self.postInit = true + self:TriggerEvent("AceEvent_FullyInitialized") + if self.registry["CHAT_MSG_CHANNEL_NOTICE"] and self.registry["CHAT_MSG_CHANNEL_NOTICE"][self] then + self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE") + end + if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then + self:UnregisterEvent("MEETINGSTONE_CHANGED") + end + if self.registry["MINIMAP_ZONE_CHANGED"] and self.registry["MINIMAP_ZONE_CHANGED"][self] then + self:UnregisterEvent("MINIMAP_ZONE_CHANGED") + end + if self.registry["LANGUAGE_LIST_CHANGED"] and self.registry["LANGUAGE_LIST_CHANGED"][self] then + self:UnregisterEvent("LANGUAGE_LIST_CHANGED") + end + collectgarbage('collect') + end + registeringFromAceEvent = true + local f = function() + self.playerLogin = true + self:ScheduleEvent("AceEvent_FullyInitialized", func, 1) + end + self:RegisterEvent("MEETINGSTONE_CHANGED", f, true) + self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function() + self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.05) + end) + self:RegisterEvent("LANGUAGE_LIST_CHANGED", function() + if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then + registeringFromAceEvent = true + self:UnregisterEvent("MEETINGSTONE_CHANGED") + self:RegisterEvent("MINIMAP_ZONE_CHANGED", f, true) + registeringFromAceEvent = nil + end + end) + self:ScheduleEvent("AceEvent_FullyInitialized", func, 10) + registeringFromAceEvent = nil + end + + registeringFromAceEvent = true + self:RegisterEvent("PLAYER_REGEN_ENABLED") + self:RegisterEvent("PLAYER_REGEN_DISABLED") + inCombat = InCombatLockdown() + registeringFromAceEvent = nil + + self:activate(oldLib, oldDeactivate) + if oldLib then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, activate) diff --git a/libs/AceLibrary/AceLibrary.lua b/libs/AceLibrary/AceLibrary.lua new file mode 100644 index 0000000..d7c78ce --- /dev/null +++ b/libs/AceLibrary/AceLibrary.lua @@ -0,0 +1,702 @@ +--[[ +Name: AceLibrary +Revision: $Rev: 23383 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Iriel (iriel@vigilance-committee.org) + Tekkub (tekkub@gmail.com) + Revision: $Rev: 23383 $ +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceLibrary +SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary +Description: Versioning library to handle other library instances, upgrading, + and proper access. + It also provides a base for libraries to work off of, providing + proper error tools. It is handy because all the errors occur in the + file that called it, not in the library file itself. +Dependencies: None +License: LGPL v2.1 +]] + +local ACELIBRARY_MAJOR = "AceLibrary" +local ACELIBRARY_MINOR = "$Revision: 23383 $" + +local _G = getfenv(0) +local previous = _G[ACELIBRARY_MAJOR] +if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end + +local function safecall(func,...) + local success, err = pcall(func,...) + if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("\n(.-: )in.-\n") or "") .. err) end +end + +-- @table AceLibrary +-- @brief System to handle all versioning of libraries. +local AceLibrary = {} +local AceLibrary_mt = {} +setmetatable(AceLibrary, AceLibrary_mt) + +local function error(self, message, ...) + if type(self) ~= "table" then + return _G.error(("Bad argument #1 to `error' (table expected, got %s)"):format(type(self)), 2) + end + + local stack = debugstack() + if not message then + local _,_,second = stack:find("\n(.-)\n") + message = "error raised! " .. second + else + local arg = { ... } -- not worried about table creation, as errors don't happen often + + for i = 1, #arg do + arg[i] = tostring(arg[i]) + end + for i = 1, 10 do + table.insert(arg, "nil") + end + message = message:format(unpack(arg)) + end + + if getmetatable(self) and getmetatable(self).__tostring then + message = ("%s: %s"):format(tostring(self), message) + elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then + message = ("%s: %s"):format(self:GetLibraryVersion(), message) + elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then + message = ("%s: %s"):format(self.class:GetLibraryVersion(), message) + end + + local first = stack:gsub("\n.*", "") + local file = first:gsub(".*\\(.*).lua:%d+: .*", "%1") + file = file:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") + + + local i = 0 + for s in stack:gmatch("\n([^\n]*)") do + i = i + 1 + if not s:find(file .. "%.lua:%d+:") and not s:find("%(tail call%)") then + file = s:gsub("^.*\\(.*).lua:%d+: .*", "%1") + file = file:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") + break + end + end + local j = 0 + for s in stack:gmatch("\n([^\n]*)") do + j = j + 1 + if j > i and not s:find(file .. "%.lua:%d+:") and not s:find("%(tail call%)") then + return _G.error(message, j+1) + end + end + return _G.error(message, 2) +end + +local function assert(self, condition, message, ...) + if not condition then + if not message then + local stack = debugstack() + local _,_,second = stack:find("\n(.-)\n") + message = "assertion failed! " .. second + end + return error(self, message, ...) + end + return condition +end + +local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5) + if type(num) ~= "number" then + return error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num)) + elseif type(kind) ~= "string" then + return error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind)) + end + local errored = false + arg = type(arg) + if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then + local stack = debugstack() + local _,_,func = stack:find("`argCheck'.-([`<].-['>])") + if not func then + _,_,func = stack:find("([`<].-['>])") + end + if kind5 then + return error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg) + elseif kind4 then + return error(self, "Bad argument #%s to %s (%s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, arg) + elseif kind3 then + return error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg) + elseif kind2 then + return error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg) + else + return error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg) + end + end +end + +local pcall +do + local function check(self, ret, ...) + if not ret then + local s = ... + return error(self, (s:gsub(".-%.lua:%d-: ", ""))) + else + return ... + end + end + + function pcall(self, func, ...) + return check(self, _G.pcall(func, ...)) + end +end + +local recurse = {} +local function addToPositions(t, major) + if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then + rawset(t, recurse, true) + AceLibrary.positions[t] = major + for k,v in pairs(t) do + if type(v) == "table" and not rawget(v, recurse) then + addToPositions(v, major) + end + if type(k) == "table" and not rawget(k, recurse) then + addToPositions(k, major) + end + end + local mt = getmetatable(t) + if mt and not rawget(mt, recurse) then + addToPositions(mt, major) + end + rawset(t, recurse, nil) + end +end + +local function svnRevisionToNumber(text) + if type(text) == "string" then + if text:find("^%$Revision: (%d+) %$$") then + return tonumber((text:gsub("^%$Revision: (%d+) %$$", "%1"))) + elseif text:find("^%$Rev: (%d+) %$$") then + return tonumber((text:gsub("^%$Rev: (%d+) %$$", "%1"))) + elseif text:find("^%$LastChangedRevision: (%d+) %$$") then + return tonumber((text:gsub("^%$LastChangedRevision: (%d+) %$$", "%1"))) + end + elseif type(text) == "number" then + return text + end + return nil +end + +local crawlReplace +do + local recurse = {} + local function func(t, to, from) + if recurse[t] then + return + end + recurse[t] = true + local mt = getmetatable(t) + setmetatable(t, nil) + rawset(t, to, rawget(t, from)) + rawset(t, from, nil) + for k,v in pairs(t) do + if v == from then + t[k] = to + elseif type(v) == "table" then + if not recurse[v] then + func(v, to, from) + end + end + + if type(k) == "table" then + if not recurse[k] then + func(k, to, from) + end + end + end + setmetatable(t, mt) + if mt then + if mt == from then + setmetatable(t, to) + elseif not recurse[mt] then + func(mt, to, from) + end + end + end + function crawlReplace(t, to, from) + func(t, to, from) + for k in pairs(recurse) do + recurse[k] = nil + end + end +end + +-- @function destroyTable +-- @brief remove all the contents of a table +-- @param t table to destroy +local function destroyTable(t) + setmetatable(t, nil) + for k,v in pairs(t) do + t[k] = nil + end +end + +local function isFrame(frame) + return type(frame) == "table" and type(rawget(frame, 0)) == "userdata" and type(rawget(frame, 'IsFrameType')) == "function" and getmetatable(frame) and type(rawget(getmetatable(frame), '__index')) == "function" +end + +-- @function copyTable +-- @brief Create a shallow copy of a table and return it. +-- @param from The table to copy from +-- @return A shallow copy of the table +local function copyTable(from) + local to = {} + for k,v in pairs(from) do + to[k] = v + end + setmetatable(to, getmetatable(from)) + return to +end + +-- @function deepTransfer +-- @brief Fully transfer all data, keeping proper previous table +-- backreferences stable. +-- @param to The table with which data is to be injected into +-- @param from The table whose data will be injected into the first +-- @param saveFields If available, a shallow copy of the basic data is saved +-- in here. +-- @param list The account of table references +-- @param list2 The current status on which tables have been traversed. +local deepTransfer +do + -- @function examine + -- @brief Take account of all the table references to be shared + -- between the to and from tables. + -- @param to The table with which data is to be injected into + -- @param from The table whose data will be injected into the first + -- @param list An account of the table references + local function examine(to, from, list, major) + list[from] = to + for k,v in pairs(from) do + if rawget(to, k) and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then + if from[k] == to[k] then + list[from[k]] = to[k] + elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then + list[from[k]] = from[k] + elseif not list[from[k]] then + examine(to[k], from[k], list, major) + end + end + end + return list + end + + function deepTransfer(to, from, saveFields, major, list, list2) + setmetatable(to, nil) + if not list then + list = {} + list2 = {} + examine(to, from, list, major) + end + list2[to] = to + for k,v in pairs(to) do + if type(rawget(from, k)) ~= "table" or type(v) ~= "table" or isFrame(v) then + if saveFields then + saveFields[k] = v + end + to[k] = nil + elseif v ~= _G then + if saveFields then + saveFields[k] = copyTable(v) + end + end + end + for k in pairs(from) do + if rawget(to, k) and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then + if not list2[to[k]] then + deepTransfer(to[k], from[k], nil, major, list, list2) + end + to[k] = list[to[k]] or list2[to[k]] + else + rawset(to, k, from[k]) + end + end + setmetatable(to, getmetatable(from)) + local mt = getmetatable(to) + if mt then + if list[mt] then + setmetatable(to, list[mt]) + elseif mt.__index and list[mt.__index] then + mt.__index = list[mt.__index] + end + end + destroyTable(from) + end +end + +-- @method TryToLoadStandalone +-- @brief Attempt to find and load a standalone version of the requested library +-- @param major A string representing the major version +-- @return If library is found, return values from the call to LoadAddOn are returned +-- If the library has been requested previously, nil is returned. +local function TryToLoadStandalone(major) + if not AceLibrary.scannedlibs then AceLibrary.scannedlibs = {} end + if AceLibrary.scannedlibs[major] then return end + + AceLibrary.scannedlibs[major] = true + + local name, _, _, enabled, loadable = GetAddOnInfo(major) + if loadable then + return LoadAddOn(name) + end + + for i=1,GetNumAddOns() do + if GetAddOnMetadata(i, "X-AceLibrary-"..major) then + local name, _, _, enabled, loadable = GetAddOnInfo(i) + if loadable then + return LoadAddOn(name) + end + end + end +end + +-- @method IsNewVersion +-- @brief Obtain whether the supplied version would be an upgrade to the +-- current version. This allows for bypass code in library +-- declaration. +-- @param major A string representing the major version +-- @param minor An integer or an svn revision string representing the minor version +-- @return whether the supplied version would be newer than what is +-- currently available. +function AceLibrary:IsNewVersion(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(("Bad argument #3 to `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) + end + end + argCheck(self, minor, 3, "number") + local data = self.libs[major] + if not data then + return true + end + return data.minor < minor +end + +-- @method HasInstance +-- @brief Returns whether an instance exists. This allows for optional support of a library. +-- @param major A string representing the major version. +-- @param minor (optional) An integer or an svn revision string representing the minor version. +-- @return Whether an instance exists. +function AceLibrary:HasInstance(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + if minor then + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(("Bad argument #3 to `HasInstance'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) + end + end + argCheck(self, minor, 3, "number") + if not self.libs[major] then + return + end + return self.libs[major].minor == minor + end + return self.libs[major] and true +end + +-- @method GetInstance +-- @brief Returns the library with the given major/minor version. +-- @param major A string representing the major version. +-- @param minor (optional) An integer or an svn revision string representing the minor version. +-- @return The library with the given major/minor version. +function AceLibrary:GetInstance(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + local data = self.libs[major] + if not data then + _G.error(("Cannot find a library instance of %s."):format(major), 2) + return + end + if minor then + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(("Bad argument #3 to `GetInstance'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) + end + end + argCheck(self, minor, 2, "number") + if data.minor ~= minor then + _G.error(("Cannot find a library instance of %s, minor version %d."):format(major, minor), 2) + end + end + return data.instance +end + +-- Syntax sugar. AceLibrary("FooBar-1.0") +AceLibrary_mt.__call = AceLibrary.GetInstance + +local donothing = function() end + +local AceEvent + +-- @method Register +-- @brief Registers a new version of a given library. +-- @param newInstance the library to register +-- @param major the major version of the library +-- @param minor the minor version of the library +-- @param activateFunc (optional) A function to be called when the library is +-- fully activated. Takes the arguments +-- (newInstance [, oldInstance, oldDeactivateFunc]). If +-- oldInstance is given, you should probably call +-- oldDeactivateFunc(oldInstance). +-- @param deactivateFunc (optional) A function to be called by a newer library's +-- activateFunc. +-- @param externalFunc (optional) A function to be called whenever a new +-- library is registered. +function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc) + argCheck(self, newInstance, 2, "table") + argCheck(self, major, 3, "string") + if major ~= ACELIBRARY_MAJOR then + for k,v in pairs(_G) do + if v == newInstance then + geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k)) + end + end + end + if major ~= ACELIBRARY_MAJOR and not major:find("^[%a%-][%a%d%-]*%-%d+%.%d+$") then + _G.error(string.format("Bad argument #3 to `Register'. Must be in the form of \"Name-1.0\". %q is not appropriate", major), 2) + end + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) + end + end + argCheck(self, minor, 4, "number") + if math.floor(minor) ~= minor or minor < 0 then + error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor) + end + argCheck(self, activateFunc, 5, "function", "nil") + argCheck(self, deactivateFunc, 6, "function", "nil") + argCheck(self, externalFunc, 7, "function", "nil") + if not deactivateFunc then + deactivateFunc = donothing + end + local data = self.libs[major] + if not data then + -- This is new + local instance = copyTable(newInstance) + crawlReplace(instance, instance, newInstance) + destroyTable(newInstance) + if AceLibrary == newInstance then + self = instance + AceLibrary = instance + end + self.libs[major] = { + instance = instance, + minor = minor, + deactivateFunc = deactivateFunc, + externalFunc = externalFunc, + } + rawset(instance, 'GetLibraryVersion', function(self) + return major, minor + end) + if not rawget(instance, 'error') then + rawset(instance, 'error', error) + end + if not rawget(instance, 'assert') then + rawset(instance, 'assert', assert) + end + if not rawget(instance, 'argCheck') then + rawset(instance, 'argCheck', argCheck) + end + if not rawget(instance, 'pcall') then + rawset(instance, 'pcall', pcall) + end + addToPositions(instance, major) + if activateFunc then + safecall(activateFunc, instance, nil, nil) -- no old version, so explicit nil + +--[[ if major ~= ACELIBRARY_MAJOR then + for k,v in pairs(_G) do + if v == instance then + geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k)) + end + end + end]] + end + + if externalFunc then + for k,data in pairs(self.libs) do + if k ~= major then + safecall(externalFunc, instance, k, data.instance) + end + end + end + + for k,data in pairs(self.libs) do + if k ~= major and data.externalFunc then + safecall(data.externalFunc, data.instance, major, instance) + end + end + if major == "AceEvent-2.0" then + AceEvent = instance + end + if AceEvent then + AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance) + end + + return instance + end + local instance = data.instance + if minor <= data.minor then + -- This one is already obsolete, raise an error. + _G.error(("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end"):format(major, data.minor, minor, major, minor), 2) + return + end + -- This is an update + local oldInstance = {} + + addToPositions(newInstance, major) + local isAceLibrary = (AceLibrary == newInstance) + local old_error, old_assert, old_argCheck, old_pcall + if isAceLibrary then + self = instance + AceLibrary = instance + + old_error = instance.error + old_assert = instance.assert + old_argCheck = instance.argCheck + old_pcall = instance.pcall + + self.error = error + self.assert = assert + self.argCheck = argCheck + self.pcall = pcall + end + deepTransfer(instance, newInstance, oldInstance, major) + crawlReplace(instance, instance, newInstance) + local oldDeactivateFunc = data.deactivateFunc + data.minor = minor + data.deactivateFunc = deactivateFunc + data.externalFunc = externalFunc + rawset(instance, 'GetLibraryVersion', function(self) + return major, minor + end) + if not rawget(instance, 'error') then + rawset(instance, 'error', error) + end + if not rawget(instance, 'assert') then + rawset(instance, 'assert', assert) + end + if not rawget(instance, 'argCheck') then + rawset(instance, 'argCheck', argCheck) + end + if not rawget(instance, 'pcall') then + rawset(instance, 'pcall', pcall) + end + if isAceLibrary then + for _,v in pairs(self.libs) do + local i = type(v) == "table" and v.instance + if type(i) == "table" then + if not rawget(i, 'error') or i.error == old_error then + rawset(i, 'error', error) + end + if not rawget(i, 'assert') or i.assert == old_assert then + rawset(i, 'assert', assert) + end + if not rawget(i, 'argCheck') or i.argCheck == old_argCheck then + rawset(i, 'argCheck', argCheck) + end + if not rawget(i, 'pcall') or i.pcall == old_pcall then + rawset(i, 'pcall', pcall) + end + end + end + end + if activateFunc then + safecall(activateFunc, instance, oldInstance, oldDeactivateFunc) + +--[[ if major ~= ACELIBRARY_MAJOR then + for k,v in pairs(_G) do + if v == instance then + geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k)) + end + end + end]] + else + safecall(oldDeactivateFunc, oldInstance) + end + oldInstance = nil + + if externalFunc then + for k,data in pairs(self.libs) do + if k ~= major then + safecall(externalFunc, instance, k, data.instance) + end + end + end + + return instance +end + +local iter +function AceLibrary:IterateLibraries() + if not iter then + local function iter(t, k) + k = next(t, k) + if not k then + return nil + else + return k, t[k].instance + end + end + end + return iter, self.libs, nil +end + +-- @function Activate +-- @brief The activateFunc for AceLibrary itself. Called when +-- AceLibrary properly registers. +-- @param self Reference to AceLibrary +-- @param oldLib (optional) Reference to an old version of AceLibrary +-- @param oldDeactivate (optional) Function to deactivate the old lib +local function activate(self, oldLib, oldDeactivate) + AceLibrary = self + if not self.libs then + self.libs = oldLib and oldLib.libs or {} + self.scannedlibs = oldLib and oldLib.scannedlibs or {} + end + if not self.positions then + self.positions = oldLib and oldLib.positions or setmetatable({}, { __mode = "k" }) + end + + -- Expose the library in the global environment + _G[ACELIBRARY_MAJOR] = self + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +if not previous then + previous = AceLibrary +end +if not previous.libs then + previous.libs = {} +end +AceLibrary.libs = previous.libs +if not previous.positions then + previous.positions = setmetatable({}, { __mode = "k" }) +end +AceLibrary.positions = previous.positions +AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate) diff --git a/libs/AceOO-2.0/AceOO-2.0.lua b/libs/AceOO-2.0/AceOO-2.0.lua new file mode 100644 index 0000000..b694b6a --- /dev/null +++ b/libs/AceOO-2.0/AceOO-2.0.lua @@ -0,0 +1,986 @@ +--[[ +Name: AceOO-2.0 +Revision: $Rev: 23383 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceOO-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceOO-2.0 +Description: Library to provide an object-orientation framework. +Dependencies: AceLibrary +License: MIT +]] + +local MAJOR_VERSION = "AceOO-2.0" +local MINOR_VERSION = "$Revision: 23383 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +local AceOO = { + error = AceLibrary.error, + argCheck = AceLibrary.argCheck +} + +-- @function getuid +-- @brief Obtain a unique string identifier for the object in question. +-- @param t The object to obtain the uid for. +-- @return The uid string. +local function pad(cap) + return ("0"):rep(8 - cap:len()) .. cap +end +local function getuid(t) + local mt = getmetatable(t) + setmetatable(t, nil) + local str = tostring(t) + setmetatable(t, mt) + local cap = str:match("[^:]*: 0x(.*)$") + if not cap then + cap = str:match("[^:]*: (.*)$") + end + if cap then + return pad(cap) + end +end + +local function getlibrary(o) + if type(o) == "table" then + return o + elseif type(o) == "string" then + if not AceLibrary:HasInstance(o) then + AceOO:error("Library %q does not exist.", o) + end + return AceLibrary(o) + end +end + +local function deeprawget(self, k) + while true do + local v = rawget(self, k) + if v ~= nil then + return v + end + local mt = getmetatable(self) + if not mt or type(mt.__index) ~= "table" then + return nil + end + self = mt.__index + end +end + +-- @function Factory +-- @brief Construct a factory for the creation of objects. +-- @param obj The object whose init method will be called on the new factory +-- object. +-- @param newobj The object whose init method will be called on the new +-- objects that the Factory creates, to initialize them. +-- @param (...) Arguments which will be passed to obj.init() in addition +-- to the Factory object. +-- @return The new factory which creates a newobj when its new method is called, +-- or when it is called directly (__call metamethod). +local Factory +do + local function getlibraries(...) + if select('#', ...) == 0 then + return + end + return getlibrary((select(1, ...))), getlibraries(select(2, ...)) + end + local arg = {} + local function new(obj, ...) + local t = {} + local uid = getuid(t) + obj:init(t, getlibraries(...)) + t.uid = uid + return t + end + + local function createnew(self, ...) + local o = self.prototype + local x = new(o, getlibraries(...)) + return x + end + + function Factory(obj, newobj, ...) + local t = new(obj, ...) + t.prototype = newobj + t.new = createnew + getmetatable(t).__call = t.new + return t + end +end + + +local function objtostring(self) + if self.ToString then + return self:ToString() + elseif self.GetLibraryVersion then + return (self:GetLibraryVersion()) + elseif self.super then + local s = "Sub-" .. tostring(self.super) + local first = true + if self.interfaces then + for interface in pairs(self.interfaces) do + if first then + s = s .. "(" .. tostring(interface) + first = false + else + s = s .. ", " .. tostring(interface) + end + end + end + if self.mixins then + for mixin in pairs(self.mixins) do + if first then + s = s .. tostring(mixin) + first = false + else + s = s .. ", " .. tostring(mixin) + end + end + end + if first then + if self.uid then + return s .. ":" .. self.uid + else + return s + end + else + return s .. ")" + end + else + return self.uid and 'Subclass:' .. self.uid or 'Subclass' + end +end + +-- @table Object +-- @brief Base of all objects, including Class. +-- +-- @method init +-- @brief Initialize a new object. +-- @param newobject The object to initialize +-- @param class The class to make newobject inherit from +local Object +do + Object = {} + function Object:init(newobject, class) + local parent = class or self + if not rawget(newobject, 'uid') then + newobject.uid = getuid(newobject) + end + local mt = { + __index = parent, + __tostring = objtostring, + } + setmetatable(newobject, mt) + end + Object.uid = getuid(Object) + setmetatable(Object, { __tostring = function() return 'Object' end }) +end + +local Interface + +local function validateInterface(object, interface) + if not object.class and object.prototype then + object = object.prototype + end + for k,v in pairs(interface.interface) do + if tostring(type(object[k])) ~= v then + return false + end + end + if interface.superinterfaces then + for superinterface in pairs(interface.superinterfaces) do + if not validateInterface(object, superinterface) then + return false + end + end + end + if type(object.class) == "table" and rawequal(object.class.prototype, object) then + if not object.class.interfaces then + rawset(object.class, 'interfaces', {}) + end + object.class.interfaces[interface] = true + elseif type(object.class) == "table" and type(object.class.prototype) == "table" then + validateInterface(object.class.prototype, interface) + -- check if class is proper, thus preventing future checks. + end + return true +end + +-- @function inherits +-- @brief Return whether an Object or Class inherits from a given +-- parent. +-- @param object Object or Class to check +-- @param parent Parent to test inheritance from +-- @return whether an Object or Class inherits from a given +-- parent. +local function inherits(object, parent) + object = getlibrary(object) + if type(parent) == "string" then + if not AceLibrary:HasInstance(parent) then + return false + else + parent = AceLibrary(parent) + end + end + AceOO:argCheck(parent, 2, "table") + if type(object) ~= "table" then + return false + end + local current + local class = deeprawget(object, 'class') + if class then + current = class + else + current = object + end + if type(current) ~= "table" then + return false + end + if rawequal(current, parent) then + return true + end + if parent.class then + while true do + if rawequal(current, Object) then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if rawequal(mixin, parent) then + return true + end + end + end + if current.interfaces then + for interface in pairs(current.interfaces) do + if rawequal(interface, parent) then + return true + end + end + end + current = deeprawget(current, 'super') + if type(current) ~= "table" then + break + end + end + + local isInterface = false + local curr = parent.class + while true do + if rawequal(curr, Object) then + break + elseif rawequal(curr, Interface) then + isInterface = true + break + end + curr = deeprawget(curr, 'super') + if type(curr) ~= "table" then + break + end + end + return isInterface and validateInterface(object, parent) + else + while true do + if rawequal(current, parent) then + return true + elseif rawequal(current, Object) then + return false + end + current = deeprawget(current, 'super') + if type(current) ~= "table" then + return false + end + end + end +end + +-- @table Class +-- @brief An object factory which sets up inheritence and supports +-- 'mixins'. +-- +-- @metamethod Class call +-- @brief Call ClassFactory:new() to create a new class. +-- +-- @method Class new +-- @brief Construct a new object. +-- @param (...) Arguments to pass to the object init function. +-- @return The new object. +-- +-- @method Class init +-- @brief Initialize a new class. +-- @param parent Superclass. +-- @param (...) Mixins. +-- +-- @method Class ToString +-- @return A string representing the object, in this case 'Class'. +local initStatus +local Class +local Mixin +local autoEmbed = false +local function traverseInterfaces(bit, total) + if bit.superinterfaces then + for interface in pairs(bit.superinterfaces) do + if not total[interface] then + total[interface] = true + traverseInterfaces(interface, total) + end + end + end +end +local class_new +do + Class = Factory(Object, setmetatable({}, {__index = Object}), Object) + Class.super = Object + + local function protostring(t) + return '<' .. tostring(t.class) .. ' prototype>' + end + local function classobjectstring(t) + if t.ToString then + return t:ToString() + elseif t.GetLibraryVersion then + return (t:GetLibraryVersion()) + else + return '<' .. tostring(t.class) .. ' instance>' + end + end + local function classobjectequal(self, other) + if type(self) == "table" and self.Equals then + return self:Equals(other) + elseif type(other) == "table" and other.Equals then + return other:Equals(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) == 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) == 0 + else + return rawequal(self, other) + end + end + local function classobjectlessthan(self, other) + if type(self) == "table" and self.IsLessThan then + return self:IsLessThan(other) + elseif type(other) == "table" and other.IsLessThanOrEqualTo then + return not other:IsLessThanOrEqualTo(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) < 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) > 0 + elseif type(other) == "table" and other.IsLessThan and other.Equals then + return other:Equals(self) or other:IsLessThan(self) + else + AceOO:error("cannot compare two objects") + end + end + local function classobjectlessthanequal(self, other) + if type(self) == "table" and self.IsLessThanOrEqualTo then + return self:IsLessThanOrEqualTo(other) + elseif type(other) == "table" and other.IsLessThan then + return not other:IsLessThan(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) <= 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) >= 0 + elseif type(self) == "table" and self.IsLessThan and self.Equals then + return self:Equals(other) or self:IsLessThan(other) + else + AceOO:error("cannot compare two incompatible objects") + end + end + local function classobjectadd(self, other) + if type(self) == "table" and self.Add then + return self:Add(other) + else + AceOO:error("cannot add two incompatible objects") + end + end + local function classobjectsub(self, other) + if type(self) == "table" and self.Subtract then + return self:Subtract(other) + else + AceOO:error("cannot subtract two incompatible objects") + end + end + local function classobjectunm(self, other) + if type(self) == "table" and self.UnaryNegation then + return self:UnaryNegation(other) + else + AceOO:error("attempt to negate an incompatible object") + end + end + local function classobjectmul(self, other) + if type(self) == "table" and self.Multiply then + return self:Multiply(other) + else + AceOO:error("cannot multiply two incompatible objects") + end + end + local function classobjectdiv(self, other) + if type(self) == "table" and self.Divide then + return self:Divide(other) + else + AceOO:error("cannot divide two incompatible objects") + end + end + local function classobjectpow(self, other) + if type(self) == "table" and self.Exponent then + return self:Exponent(other) + else + AceOO:error("cannot exponentiate two incompatible objects") + end + end + local function classobjectconcat(self, other) + if type(self) == "table" and self.Concatenate then + return self:Concatenate(other) + else + AceOO:error("cannot concatenate two incompatible objects") + end + end + function class_new(self, ...) + if self.virtual then + AceOO:error("Cannot instantiate a virtual class.") + end + + local o = self.prototype + local newobj = {} + if o.class and o.class.instancemeta then + setmetatable(newobj, o.class.instancemeta) + else + Object:init(newobj, o) + end + + if self.interfaces and not self.interfacesVerified then + -- Verify the interfaces + + for interface in pairs(self.interfaces) do + for field,kind in pairs(interface.interface) do + if tostring(type(newobj[field])) ~= kind then + AceOO:error("Class did not satisfy all interfaces. %q is required to be a %s. It is a %s", field, kind, tostring(type(newobj[field]))) + end + end + end + self.interfacesVerified = true + end + local tmp = initStatus + initStatus = newobj + newobj:init(...) + if initStatus then + initStatus = tmp + AceOO:error("Initialization not completed, be sure to call the superclass's init method.") + return + end + initStatus = tmp + return newobj + end + local classmeta = { + __tostring = objtostring, + __call = function(self, ...) + return self:new(...) + end, + } + function Class:init(newclass, parent, ...) + parent = parent or self + + local total + + if parent.class then + total = { parent, ... } + parent = self + else + total = { ... } + end + if not inherits(parent, Class) then + AceOO:error("Classes must inherit from a proper class") + end + if parent.sealed then + AceOO:error("Cannot inherit from a sealed class") + end + for i,v in ipairs(total) do + if inherits(v, Mixin) and v.class then + if v.__deprecated then + AceOO:error(v.__deprecated) + end + if not newclass.mixins then + newclass.mixins = {} + end + if newclass.mixins[v] then + AceOO:error("Cannot explicitly inherit from the same mixin twice") + end + newclass.mixins[v] = true + elseif inherits(v, Interface) and v.class then + if not newclass.interfaces then + newclass.interfaces = {} + end + if newclass.interfaces[v] then + AceOO:error("Cannot explicitly inherit from the same interface twice") + end + newclass.interfaces[v] = true + else + AceOO:error("Classes can only inherit from one or zero classes and any number of mixins or interfaces") + end + end + if parent.interfaces then + if not newclass.interfaces then + newclass.interfaces = {} + end + for interface in pairs(parent.interfaces) do + newclass.interfaces[interface] = true + end + end + for k in pairs(total) do + total[k] = nil + end + + newclass.super = parent + + newclass.prototype = setmetatable(total, { + __index = parent.prototype, + __tostring = protostring, + }) + total = nil + + newclass.instancemeta = { + __index = newclass.prototype, + __tostring = classobjectstring, + __eq = classobjectequal, + __lt = classobjectlessthan, + __le = classobjectlessthanequal, + __add = classobjectadd, + __sub = classobjectsub, + __unm = classobjectunm, + __mul = classobjectmul, + __div = classobjectdiv, + __pow = classobjectpow, + __concat = classobjectconcat, + } + + setmetatable(newclass, classmeta) + + newclass.new = class_new + + if newclass.mixins then + -- Fold in the mixins + local err, msg + for mixin in pairs(newclass.mixins) do + local ret + autoEmbed = true + ret, msg = pcall(mixin.embed, mixin, newclass.prototype) + autoEmbed = false + if not ret then + err = true + break + end + end + + if err then + local pt = newclass.prototype + for k,v in pairs(pt) do + pt[k] = nil + end + + -- method conflict + AceOO:error(msg) + end + end + + newclass.prototype.class = newclass + + if newclass.interfaces then + for interface in pairs(newclass.interfaces) do + traverseInterfaces(interface, newclass.interfaces) + end + end + if newclass.mixins then + for mixin in pairs(newclass.mixins) do + if mixin.interfaces then + if not newclass.interfaces then + newclass.interfaces = {} + end + for interface in pairs(mixin.interfaces) do + newclass.interfaces[interface] = true + end + end + end + end + end + function Class:ToString() + if type(self.GetLibraryVersion) == "function" then + return (self:GetLibraryVersion()) + else + return "Class" + end + end + + local tmp + function Class.prototype:init() + if rawequal(self, initStatus) then + initStatus = nil + else + AceOO:error("Improper self passed to init. You must do MyClass.super.prototype.init(self, ...)", 2) + end + self.uid = getuid(self) + local current = self.class + while true do + if current == Class then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnInstanceInit) == "function" then + mixin:OnInstanceInit(self) + end + end + end + current = current.super + end + end +end + + +-- @object ClassFactory +-- @brief A factory for creating classes. Rarely used directly. +local ClassFactory = Factory(Object, Class, Object) + +function Class:new(...) + local x = ClassFactory:new(...) + if AceOO.classes then + AceOO.classes[x] = true + end + return x +end +getmetatable(Class).__call = Class.new + +-- @class Mixin +-- @brief A class to create mixin objects, which contain methods that get +-- "mixed in" to class prototypes. +-- +-- @object Mixin prototype +-- @brief The prototype that mixin objects inherit their methods from. +-- +-- @method Mixin prototype embed +-- @brief Mix in the methods of our object which are listed in our interface +-- to the supplied target table. +-- +-- @method Mixin prototype init +-- @brief Initialize the mixin object. +-- @param newobj The new object we're initializing. +-- @param interface The interface we implement (the list of methods our +-- prototype provides which should be mixed into the target +-- table by embed). +do + Mixin = Class() + function Mixin:ToString() + if self.GetLibraryVersion then + return (self:GetLibraryVersion()) + else + return 'Mixin' + end + end + local function _Embed(state, field, target) + field = next(state.export, field) + if field == nil then + return + end + + if rawget(target, field) or (target[field] and target[field] ~= state[field]) then + AceOO:error("Method conflict in attempt to mixin. Field %q", field) + end + + target[field] = state[field] + + local ret,msg = pcall(_Embed, state, field, target) + if not ret then + -- Mix in the next method according to the defined interface. If that + -- fails due to a conflict, re-raise to back out the previous mixed + -- methods. + + target[field] = nil + AceOO:error(msg) + end + end + function Mixin.prototype:embed(target) + if self.__deprecated then + AceOO:error(self.__deprecated) + end + local mt = getmetatable(target) + setmetatable(target, nil) + local err, msg = pcall(_Embed, self, nil, target) + if not err then + setmetatable(target, mt) + AceOO:error(msg) + return + end + if type(self.embedList) == "table" then + self.embedList[target] = true + end + if type(target.class) ~= "table" then + target[self] = true + end + if not autoEmbed and type(self.OnManualEmbed) == "function" then + self:OnManualEmbed(target) + end + setmetatable(target, mt) + end + + function Mixin.prototype:activate(oldLib, oldDeactivate) + if oldLib and oldLib.embedList then + for target in pairs(oldLib.embedList) do + local mt = getmetatable(target) + setmetatable(target, nil) + for field in pairs(oldLib.export) do + target[field] = nil + end + setmetatable(target, mt) + end + self.embedList = oldLib.embedList + for target in pairs(self.embedList) do + self:embed(target) + end + else + self.embedList = setmetatable({}, {__mode="k"}) + end + end + + function Mixin.prototype:init(export, ...) + AceOO:argCheck(export, 2, "table") + for k,v in pairs(export) do + if type(k) ~= "number" then + AceOO:error("All keys to argument #2 must be numbers.") + elseif type(v) ~= "string" then + AceOO:error("All values to argument #2 must be strings.") + end + end + local num = #export + for i = 1, num do + local v = export[i] + export[i] = nil + export[v] = true + end + + local interfaces + if select('#', ...) >= 1 then + interfaces = { ... } + for i,v in ipairs(interfaces) do + v = getlibrary(v) + interfaces[i] = v + if not v.class or not inherits(v, Interface) then + AceOO:error("Mixins can inherit only from interfaces") + end + end + local num = #interfaces + for i = 1, num do + local v = interfaces[i] + interfaces[i] = nil + interfaces[v] = true + end + for interface in pairs(interfaces) do + traverseInterfaces(interface, interfaces) + end + for interface in pairs(interfaces) do + for field,kind in pairs(interface.interface) do + if kind ~= "nil" then + local good = false + for bit in pairs(export) do + if bit == field then + good = true + break + end + end + if not good then + AceOO:error("Mixin does not fully accommodate field %q", field) + end + end + end + end + end + self.super = Mixin.prototype + Mixin.super.prototype.init(self) + self.export = export + self.interfaces = interfaces + end +end + +-- @class Interface +-- @brief A class to create interfaces, which contain contracts that classes +-- which inherit from this must comply with. +-- +-- @object Interface prototype +-- @brief The prototype that interface objects must adhere to. +-- +-- @method Interface prototype init +-- @brief Initialize the mixin object. +-- @param interface The interface we contract (the hash of fields forced). +-- @param (...) Superinterfaces +do + Interface = Class() + function Interface:ToString() + if self.GetLibraryVersion then + return (self:GetLibraryVersion()) + else + return 'Instance' + end + end + function Interface.prototype:init(interface, ...) + Interface.super.prototype.init(self) + AceOO:argCheck(interface, 2, "table") + for k,v in pairs(interface) do + if type(k) ~= "string" then + AceOO:error("All keys to argument #2 must be numbers.") + elseif type(v) ~= "string" then + AceOO:error("All values to argument #2 must be strings.") + elseif v ~= "nil" and v ~= "string" and v ~= "number" and v ~= "table" and v ~= "function" then + AceOO:error('All values to argument #2 must either be "nil", "string", "number", "table", or "function".') + end + end + if select('#', ...) >= 1 then + self.superinterfaces = { ... } + for i,v in ipairs(self.superinterfaces) do + v = getlibrary(v) + self.superinterfaces[i] = v + if not inherits(v, Interface) or not v.class then + AceOO:error('Cannot provide a non-Interface to inherit from') + end + end + local num = #self.superinterfaces + for i = 1, num do + local v = self.superinterfaces[i] + self.superinterfaces[i] = nil + self.superinterfaces[v] = true + end + end + self.interface = interface + end +end + +-- @function Classpool +-- @brief Obtain a read only class from our pool of classes, indexed by the +-- superclass and mixins. +-- @param sc The superclass of the class we want. +-- @param (m1..m20) Mixins of the class we want's objects. +-- @return A read only class from the class pool. +local Classpool +do + local pool = setmetatable({}, {__mode = 'v'}) + local function newindex(k, v) + AceOO:error('Attempt to modify a read-only class.') + end + local function protonewindex(k, v) + AceOO:error('Attempt to modify a read-only class prototype.') + end + local function ts(bit) + if type(bit) ~= "table" then + return tostring(bit) + elseif getmetatable(bit) and bit.__tostring then + return tostring(bit) + elseif type(bit.GetLibraryVersion) == "function" then + return bit:GetLibraryVersion() + else + return tostring(bit) + end + end + local t = {} + local function getcomplexuid(sc, ...) + if sc then + if sc.uid then + table.insert(t, sc.uid) + else + AceOO:error("%s is not an appropriate class/mixin", ts(sc)) + end + end + for i = 1, select('#', ...) do + local m = select(i, ...) + if m.uid then + table.insert(t, m.uid) + else + AceOO:error("%s is not an appropriate mixin", ts(m)) + end + end + table.sort(t) + local uid = table.concat(t, '') + local num = #t + for i = 1, num do + t[i] = nil + end + return uid + end + local classmeta + local arg = {} + function Classpool(superclass, ...) + local l = getlibrary + superclass = getlibrary(superclass) + arg = { ... } + for i, v in ipairs(arg) do + arg[i] = getlibrary(v) + end + if superclass then + if superclass.class then -- mixin + table.insert(arg, 1, superclass) + superclass = Class + end + else + superclass = Class + end + local key = getcomplexuid(superclass, unpack(arg)) + if not pool[key] then + local class = Class(superclass, unpack(arg)) + if not classmeta then + classmeta = {} + local mt = getmetatable(class) + for k,v in pairs(mt) do + classmeta[k] = v + end + classmeta.__newindex = newindex + end + -- Prevent the user from adding methods to this class. + -- NOTE: I'm not preventing modifications of existing class members, + -- but it's likely that only a truly malicious user will be doing so. + class.sealed = true + setmetatable(class, classmeta) + getmetatable(class.prototype).__newindex = protonewindex + pool[key] = class + end + return pool[key] + end +end + +AceOO.Factory = Factory +AceOO.Object = Object +AceOO.Class = Class +AceOO.Mixin = Mixin +AceOO.Interface = Interface +AceOO.Classpool = Classpool +AceOO.inherits = inherits + +-- Library handling bits + +local function activate(self, oldLib, oldDeactivate) + AceOO = self + Factory = self.Factory + Object = self.Object + Class = self.Class + ClassFactory.prototype = Class + Mixin = self.Mixin + Interface = self.Interface + Classpool = self.Classpool + + if oldLib then + self.classes = oldLib.classes + end + if not self.classes then + self.classes = setmetatable({}, {__mode="k"}) + else + for class in pairs(self.classes) do + class.new = class_new + end + end + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceOO, MAJOR_VERSION, MINOR_VERSION, activate) +AceOO = AceLibrary(MAJOR_VERSION) diff --git a/libs/Banzai-1.1/Banzai-1.1.lua b/libs/Banzai-1.1/Banzai-1.1.lua new file mode 100644 index 0000000..76d563f --- /dev/null +++ b/libs/Banzai-1.1/Banzai-1.1.lua @@ -0,0 +1,217 @@ +--[[ +Name: Banzai-1.1 +Revision: $Rev: 21576 $ +Author(s): Rabbit (rabbit.magtheridon@gmail.com), maia +Documentation: http://www.wowace.com/index.php/Banzai-1.1_API_Documentation +SVN: http://svn.wowace.com/wowace/trunk/BanzaiLib/Banzai-1.1 +Description: Aggro notification library. +Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, Roster-2.1 +]] + +--[[ +BanzaiLib is copyrighted 2006 by Rabbit. + +This addon is distributed under the terms of the Creative Commons +Attribution-NonCommercial-ShareAlike 2.0 license. + +http://creativecommons.org/licenses/by-nc-sa/2.5/ + +You may distribute and use these libraries without making your mod adhere to the +same license, as long as you preserve the license text embedded in the +libraries. + +Any and all questions regarding our stance and licensing should be +directed to the #wowace IRC channel on irc.freenode.net. +]] + +------------------------------------------------------------------------------- +-- Locals +------------------------------------------------------------------------------- + +local MAJOR_VERSION = "Banzai-1.1" +local MINOR_VERSION = "$Revision: 21576 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if AceLibrary:HasInstance("Banzai-1.0") then error(MAJOR_VERSION .. " can't run alongside Banzai-1.0.") end +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end +if not AceLibrary:HasInstance("AceEvent-2.0") then error(MAJOR_VERSION .. " requires AceEvent-2.0.") end +if not AceLibrary:HasInstance("Roster-2.1") then error(MAJOR_VERSION .. " requires Roster-2.1.") end + +local lib = {} +AceLibrary("AceEvent-2.0"):embed(lib) + +local RL = nil +local roster = nil +local playerName = nil + +------------------------------------------------------------------------------- +-- Local heap +------------------------------------------------------------------------------- + +local new, del +do + local cache = setmetatable({},{__mode='k'}) + function new() + local t = next(cache) + if t then + cache[t] = nil + return t + else + return {} + end + end + function del(t) + for k in pairs(t) do + t[k] = nil + end + cache[t] = true + return nil + end +end + +------------------------------------------------------------------------------- +-- Initialization +------------------------------------------------------------------------------- + +-- Activate a new instance of this library +function activate(self, oldLib, oldDeactivate) + if oldLib then + self.vars = oldLib.vars + if oldLib:IsEventScheduled("UpdateAggroList") then + oldLib:CancelScheduledEvent("UpdateAggroList") + self:StartOrStop() + end + else + self.vars = {} + end + + RL = AceLibrary("Roster-2.1") + roster = RL.roster + playerName = UnitName("player") + + if not self.vars then self.vars = {} end + + self:RegisterEvent("AceEvent_EventRegistered", "StartOrStop") + self:RegisterEvent("AceEvent_EventUnregistered", "StartOrStop") + + if oldDeactivate then oldDeactivate(oldLib) end +end + +------------------------------------------------------------------------------- +-- Events +------------------------------------------------------------------------------- + +function lib:StartOrStop() + local aceEvent = AceLibrary("AceEvent-2.0") + if aceEvent:IsEventRegistered("Banzai_UnitGainedAggro") or + aceEvent:IsEventRegistered("Banzai_PlayerGainedAggro") or + aceEvent:IsEventRegistered("Banzai_UnitLostAggro") or + aceEvent:IsEventRegistered("Banzai_PlayerLostAggro") or + aceEvent:IsEventRegistered("Banzai_Run") then + if not self:IsEventScheduled("UpdateAggroList") then + self:ScheduleRepeatingEvent("UpdateAggroList", self.UpdateAggroList, 0.2, self) + self.vars.running = true + self:TriggerEvent("Banzai_Enabled") + end + elseif self:IsEventRegistered("UpdateAggroList") then + self:CancelScheduledEvent("UpdateAggroList") + for i, unit in pairs(roster) do + unit.banzai = nil + unit.banzaiModifier = nil + end + self.vars.running = nil + self:TriggerEvent("Banzai_Disabled") + end +end + +------------------------------------------------------------------------------- +-- Library +------------------------------------------------------------------------------- + +function lib:UpdateAggroList() + local oldBanzai = nil + + for name, unit in pairs(roster) do + if not oldBanzai then oldBanzai = new() end + oldBanzai[name] = unit.banzai + + -- deduct aggro for all, increase it later for everyone with aggro + if not unit.banzaiModifier then unit.banzaiModifier = 0 end + unit.banzaiModifier = math.max(0, unit.banzaiModifier - 5) + + -- check for aggro + local targetId = unit.unitid .. "target" + if UnitExists(targetId) and UnitExists(targetId .. "target") then + local targetName = UnitName(targetId .. "target") + if roster[targetName] and UnitCanAttack("player", targetId) and UnitCanAttack(targetId, "player") then + if not roster[targetName].banzaiModifier then roster[targetName].banzaiModifier = 0 end + roster[targetName].banzaiModifier = roster[targetName].banzaiModifier + 10 + if not roster[targetName].banzaiTarget then roster[targetName].banzaiTarget = new() end + table.insert(roster[targetName].banzaiTarget, targetId) + end + end + + -- cleanup + unit.banzaiModifier = math.max(0, unit.banzaiModifier) + unit.banzaiModifier = math.min(25, unit.banzaiModifier) + + -- set aggro + unit.banzai = (unit.banzaiModifier > 15) + end + + for name, unit in pairs(roster) do + if oldBanzai[name] ~= nil and oldBanzai[name] ~= unit.banzai then + -- Aggro status has changed. + if unit.banzai == true and unit.banzaiTarget then + -- Unit has aggro + self:TriggerEvent("Banzai_UnitGainedAggro", unit.unitid, unit.banzaiTarget) + if name == playerName then + self:TriggerEvent("Banzai_PlayerGainedAggro", unit.banzaiTarget) + end + elseif unit.banzai == false then + -- Unit lost aggro + self:TriggerEvent("Banzai_UnitLostAggro", unit.unitid) + if name == playerName then + self:TriggerEvent("Banzai_PlayerLostAggro", unit.unitid) + end + end + end + if unit.banzaiTarget then + unit.banzaiTarget = del(unit.banzaiTarget) + end + end + + if oldBanzai then + oldBanzai = del(oldBanzai) + end +end + +------------------------------------------------------------------------------- +-- API +------------------------------------------------------------------------------- + +function lib:GetUnitAggroByUnitId( unitId ) + if not self.vars.running then error(MAJOR_VERSION.." is not running. You must register for one of the events.") end + local rosterUnit = RL:GetUnitObjectFromUnit(unitId) + if not rosterUnit then return nil end + return rosterUnit.banzai +end + +function lib:GetUnitAggroByUnitName( unitName ) + if not self.vars.running then error(MAJOR_VERSION.." is not running. You must register for one of the events.") end + local rosterUnit = RL:GetUnitObjectFromName(unitName) + if not rosterUnit then return nil end + return rosterUnit.banzai +end + +function lib:IsRunning() + return self:IsEventScheduled("UpdateAggroList") +end + +------------------------------------------------------------------------------- +-- Register +------------------------------------------------------------------------------- +AceLibrary:Register(lib, MAJOR_VERSION, MINOR_VERSION, activate) + diff --git a/libs/Dewdrop-2.0/Dewdrop-2.0.lua b/libs/Dewdrop-2.0/Dewdrop-2.0.lua new file mode 100644 index 0000000..9c917a1 --- /dev/null +++ b/libs/Dewdrop-2.0/Dewdrop-2.0.lua @@ -0,0 +1,2983 @@ +--[[ +Name: Dewdrop-2.0 +Revision: $Rev: 23612 $ +Author(s): ckknight (ckknight@gmail.com) +Website: http://ckknight.wowinterface.com/ +Documentation: http://wiki.wowace.com/index.php/Dewdrop-2.0 +SVN: http://svn.wowace.com/root/trunk/DewdropLib/Dewdrop-2.0 +Description: A library to provide a clean dropdown menu interface. +Dependencies: AceLibrary +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "Dewdrop-2.0" +local MINOR_VERSION = "$Revision: 23612 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +local Dewdrop = {} + +local CLOSE = "Close" +local CLOSE_DESC = "Close the menu." +local VALIDATION_ERROR = "Validation error." +local RESET_KEYBINDING_DESC = "Hit escape to clear the keybinding." +local KEY_BUTTON1 = "Left Mouse" +local KEY_BUTTON2 = "Right Mouse" +local DISABLED = "Disabled" +local DEFAULT_CONFIRM_MESSAGE = "Are you sure you want to perform `%s'?" + +if GetLocale() == "deDE" then + CLOSE = "Schlie\195\159en" + CLOSE_DESC = "Men\195\188 schlie\195\159en." + VALIDATION_ERROR = "Validierungsfehler." + RESET_KEYBINDING_DESC = "Escape dr\195\188cken, um die Tastenbelegung zu l\195\182schen." + KEY_BUTTON1 = "Linke Maustaste" + KEY_BUTTON2 = "Rechte Maustaste" + DISABLED = "Deaktiviert" +elseif GetLocale() == "koKR" then + CLOSE = "닫기" + CLOSE_DESC = "메뉴를 닫습니다." +elseif GetLocale() == "frFR" then + CLOSE = "Fermer" + CLOSE_DESC = "Ferme le menu." + VALIDATION_ERROR = "Erreur de validation." + RESET_KEYBINDING_DESC = "Appuyez sur la touche Echappement pour effacer le raccourci." +elseif GetLocale() == "esES" then + CLOSE = "Cerrar" + CLOSE_DESC = "Cierra el men\195\186." + VALIDATION_ERROR = "Error de validaci\195\179n" + RESET_KEYBINDING_DESC = "Pulsa Escape para limpiar la asignaci\195\179n de tecla." +end + +Dewdrop.KEY_BUTTON1 = KEY_BUTTON1 +Dewdrop.KEY_BUTTON2 = KEY_BUTTON2 + +local function new(...) + local t = {} + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t +end + +local tmp +do + local t = {} + function tmp(...) + for k in pairs(t) do + t[k] = nil + end + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t + end +end +local tmp2 +do + local t = {} + function tmp2(...) + for k in pairs(t) do + t[k] = nil + end + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t + end +end +local levels +local buttons + +local function GetScaledCursorPosition() + local x, y = GetCursorPosition() + local scale = UIParent:GetEffectiveScale() + return x / scale, y / scale +end + +local function StartCounting(self, level) + for i = level, 1, -1 do + if levels[i] then + levels[i].count = 3 + end + end +end + +local function StopCounting(self, level) + for i = level, 1, -1 do + if levels[i] then + levels[i].count = nil + end + end +end + +local function OnUpdate(self, elapsed) + for _,level in ipairs(levels) do + local count = level.count + if count then + count = count - elapsed + if count < 0 then + level.count = nil + self:Close(level.num) + else + level.count = count + end + end + end +end + +local function CheckDualMonitor(self, frame) + local ratio = GetScreenWidth() / GetScreenHeight() + if ratio >= 2.4 and frame:GetRight() > GetScreenWidth() / 2 and frame:GetLeft() < GetScreenWidth() / 2 then + local offsetx + if GetCursorPosition() / GetScreenHeight() * 768 < GetScreenWidth() / 2 then + offsetx = GetScreenWidth() / 2 - frame:GetRight() + else + offsetx = GetScreenWidth() / 2 - frame:GetLeft() + end + local point, parent, relativePoint, x, y = frame:GetPoint(1) + frame:SetPoint(point, parent, relativePoint, (x or 0) + offsetx, y or 0) + end +end + +local function CheckSize(self, level) + if not level.buttons then + return + end + local height = 20 + for _, button in ipairs(level.buttons) do + height = height + button:GetHeight() + end + level:SetHeight(height) + local width = 160 + for _, button in ipairs(level.buttons) do + local extra = 1 + if button.hasArrow or button.hasColorSwatch then + extra = extra + 16 + end + if not button.notCheckable then + extra = extra + 24 + end + button.text:SetFont(STANDARD_TEXT_FONT, button.textHeight) + if button.text:GetWidth() + extra > width then + width = button.text:GetWidth() + extra + end + end + level:SetWidth(width + 20) + if level:GetLeft() and level:GetRight() and level:GetTop() and level:GetBottom() and (level:GetLeft() < 0 or level:GetRight() > GetScreenWidth() or level:GetTop() > GetScreenHeight() or level:GetBottom() < 0) then + level:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPLEFT", level.parent or level:GetParent(), "TOPRIGHT", 5, 10) + else + level:SetPoint("BOTTOMLEFT", level.parent or level:GetParent(), "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPRIGHT", level.parent or level:GetParent(), "TOPLEFT", -5, 10) + else + level:SetPoint("BOTTOMRIGHT", level.parent or level:GetParent(), "BOTTOMLEFT", -5, -10) + end + end + end + local dirty = false + if not level:GetRight() then + self:Close() + return + end + if level:GetRight() > GetScreenWidth() and level.lastDirection == "RIGHT" then + level.lastDirection = "LEFT" + dirty = true + elseif level:GetLeft() < 0 and level.lastDirection == "LEFT" then + level.lastDirection = "RIGHT" + dirty = true + end + if level:GetTop() > GetScreenHeight() and level.lastVDirection == "UP" then + level.lastVDirection = "DOWN" + dirty = true + elseif level:GetBottom() < 0 and level.lastVDirection == "DOWN" then + level.lastVDirection = "UP" + dirty = true + end + if dirty then + level:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPLEFT", level.parent or level:GetParent(), "TOPRIGHT", 5, 10) + else + level:SetPoint("BOTTOMLEFT", level.parent or level:GetParent(), "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPRIGHT", level.parent or level:GetParent(), "TOPLEFT", -5, 10) + else + level:SetPoint("BOTTOMRIGHT", level.parent or level:GetParent(), "BOTTOMLEFT", -5, -10) + end + end + end + if level:GetTop() > GetScreenHeight() then + local top = level:GetTop() + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:ClearAllPoints() + level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) + GetScreenHeight() - top) + elseif level:GetBottom() < 0 then + local bottom = level:GetBottom() + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:ClearAllPoints() + level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) - bottom) + end + CheckDualMonitor(self, level) + if mod(level.num, 5) == 0 then + local left, bottom = level:GetLeft(), level:GetBottom() + level:ClearAllPoints() + level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + end +end + +local Open +local OpenSlider +local OpenEditBox +local Refresh +local Clear +local function ReleaseButton(self, level, index) + if not level.buttons then + return + end + if not level.buttons[index] then + return + end + local button = level.buttons[index] + button:Hide() + if button.highlight then + button.highlight:Hide() + end +-- button.arrow:SetVertexColor(1, 1, 1) +-- button.arrow:SetHeight(16) +-- button.arrow:SetWidth(16) + table.remove(level.buttons, index) + table.insert(buttons, button) + for k in pairs(button) do + if k ~= 0 and k ~= "text" and k ~= "check" and k ~= "arrow" and k ~= "colorSwatch" and k ~= "highlight" and k ~= "radioHighlight" then + button[k] = nil + end + end + return true +end + +local function Scroll(self, level, down) + if down then + if level:GetBottom() < 0 then + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:SetPoint(point, parent, relativePoint, x, y + 50) + if level:GetBottom() > 0 then + level:SetPoint(point, parent, relativePoint, x, y + 50 - level:GetBottom()) + end + end + else + if level:GetTop() > GetScreenHeight() then + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:SetPoint(point, parent, relativePoint, x, y - 50) + if level:GetTop() < GetScreenHeight() then + level:SetPoint(point, parent, relativePoint, x, y - 50 + GetScreenHeight() - level:GetTop()) + end + end + end +end + +local function getArgs(t, str, num, ...) + local x = t[str .. num] + if x == nil then + return ... + else + return x, getArgs(t, str, num + 1, ...) + end +end + +local sliderFrame +local editBoxFrame + +local function showGameTooltip(this) + if this.tooltipTitle or this.tooltipText then + GameTooltip_SetDefaultAnchor(GameTooltip, this) + local disabled = not this.isTitle and this.disabled + if this.tooltipTitle then + if disabled then + GameTooltip:SetText(this.tooltipTitle, 0.5, 0.5, 0.5, 1) + else + GameTooltip:SetText(this.tooltipTitle, 1, 1, 1, 1) + end + if this.tooltipText then + if disabled then + GameTooltip:AddLine(this.tooltipText, (NORMAL_FONT_COLOR.r + 0.5) / 2, (NORMAL_FONT_COLOR.g + 0.5) / 2, (NORMAL_FONT_COLOR.b + 0.5) / 2, 1) + else + GameTooltip:AddLine(this.tooltipText, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + end + end + else + if disabled then + GameTooltip:SetText(this.tooltipText, 0.5, 0.5, 0.5, 1) + else + GameTooltip:SetText(this.tooltipText, 1, 1, 1, 1) + end + end + GameTooltip:Show() + end + if this.tooltipFunc then + GameTooltip:SetOwner(this, "ANCHOR_NONE") + GameTooltip:SetPoint("TOPLEFT", this, "TOPRIGHT", 5, 0) + this.tooltipFunc(getArgs(this, 'tooltipArg', 1)) + GameTooltip:Show() + end +end + +local tmpt = setmetatable({}, {mode='v'}) +local numButtons = 0 +local function AcquireButton(self, level) + if not levels[level] then + return + end + level = levels[level] + if not level.buttons then + level.buttons = {} + end + local button + if #buttons == 0 then + numButtons = numButtons + 1 + button = CreateFrame("Button", "Dewdrop20Button" .. numButtons, nil) + button:SetFrameStrata("FULLSCREEN_DIALOG") + button:SetHeight(16) + local highlight = button:CreateTexture(nil, "BACKGROUND") + highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + button.highlight = highlight + highlight:SetBlendMode("ADD") + highlight:SetAllPoints(button) + highlight:Hide() + local check = button:CreateTexture(nil, "ARTWORK") + button.check = check + check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + check:SetPoint("CENTER", button, "LEFT", 12, 0) + check:SetWidth(24) + check:SetHeight(24) + local radioHighlight = button:CreateTexture(nil, "ARTWORK") + button.radioHighlight = radioHighlight + radioHighlight:SetTexture("Interface\\Buttons\\UI-RadioButton") + radioHighlight:SetAllPoints(check) + radioHighlight:SetBlendMode("ADD") + radioHighlight:SetTexCoord(0.5, 0.75, 0, 1) + radioHighlight:Hide() + button:SetScript("OnEnter", function() + if (sliderFrame and sliderFrame:IsShown() and sliderFrame.mouseDown and sliderFrame.level == this.level.num + 1) or (editBoxFrame and editBoxFrame:IsShown() and editBoxFrame.mouseDown and editBoxFrame.level == this.level.num + 1) then + for i = 1, this.level.num do + Refresh(self, levels[i]) + end + return + end + self:Close(this.level.num + 1) + if not this.disabled then + if this.hasSlider then + OpenSlider(self, this) + elseif this.hasEditBox then + OpenEditBox(self, this) + elseif this.hasArrow then + Open(self, this, nil, this.level.num + 1, this.value) + end + end + if not this.level then -- button reclaimed + return + end + StopCounting(self, this.level.num + 1) + if not this.disabled then + highlight:Show() + if this.isRadio then + button.radioHighlight:Show() + end + end + showGameTooltip(this) + end) + button:SetScript("OnLeave", function() + if not this.selected then + highlight:Hide() + end + button.radioHighlight:Hide() + if this.level then + StartCounting(self, this.level.num) + end + GameTooltip:Hide() + end) + local first = true + button:SetScript("OnClick", function() + if not this.disabled then + if this.hasColorSwatch then + local func = button.colorFunc + local hasOpacity = this.hasOpacity + local this = this + for k in pairs(tmpt) do + tmpt[k] = nil + end + for i = 1, 1000 do + local x = this['colorArg'..i] + if x == nil then + break + else + tmpt[i] = x + end + end + ColorPickerFrame.func = function() + if func then + local r,g,b = ColorPickerFrame:GetColorRGB() + local a = hasOpacity and 1 - OpacitySliderFrame:GetValue() or nil + local n = #tmpt + tmpt[n+1] = r + tmpt[n+2] = g + tmpt[n+3] = b + tmpt[n+4] = a + func(unpack(tmpt)) + tmpt[n+1] = nil + tmpt[n+2] = nil + tmpt[n+3] = nil + tmpt[n+4] = nil + end + end + ColorPickerFrame.hasOpacity = this.hasOpacity + ColorPickerFrame.opacityFunc = ColorPickerFrame.func + ColorPickerFrame.opacity = 1 - this.opacity + ColorPickerFrame:SetColorRGB(this.r, this.g, this.b) + local r, g, b, a = this.r, this.g, this.b, this.opacity + ColorPickerFrame.cancelFunc = function() + if func then + local n = #tmpt + tmpt[n+1] = r + tmpt[n+2] = g + tmpt[n+3] = b + tmpt[n+4] = a + func(unpack(tmpt)) + for i = 1, n+4 do + tmpt[i] = nil + end + end + end + self:Close(1) + ShowUIPanel(ColorPickerFrame) + elseif this.func then + local level = this.level + if type(this.func) == "string" then + self:assert(type(this.arg1[this.func]) == "function", "Cannot call method " .. this.func) + this.arg1[this.func](this.arg1, getArgs(this, 'arg', 2)) + else + this.func(getArgs(this, 'arg', 1)) + end + if this.closeWhenClicked then + self:Close() + elseif level:IsShown() then + for i = 1, level.num do + Refresh(self, levels[i]) + end + local value = levels[level.num].value + for i = level.num-1, 1, -1 do + local level = levels[i] + local good = false + for _,button in ipairs(level.buttons) do + if button.value == value then + good = true + break + end + end + if not good then + Dewdrop:Close(i+1) + end + value = levels[i].value + end + end + elseif this.closeWhenClicked then + self:Close() + end + end + end) + local text = button:CreateFontString(nil, "ARTWORK") + button.text = text + text:SetFontObject(GameFontHighlightSmall) + button.text:SetFont(STANDARD_TEXT_FONT, UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT) + button:SetScript("OnMouseDown", function() + if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then + text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 1 or 25, -1) + end + end) + button:SetScript("OnMouseUp", function() + if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then + text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 0 or 24, 0) + end + end) + local arrow = button:CreateTexture(nil, "ARTWORK") + button.arrow = arrow + arrow:SetPoint("LEFT", button, "RIGHT", -16, 0) + arrow:SetWidth(16) + arrow:SetHeight(16) + arrow:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") + local colorSwatch = button:CreateTexture(nil, "OVERLAY") + button.colorSwatch = colorSwatch + colorSwatch:SetWidth(20) + colorSwatch:SetHeight(20) + colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") + local texture = button:CreateTexture(nil, "OVERLAY") + colorSwatch.texture = texture + texture:SetTexture(1, 1, 1) + texture:SetWidth(11.5) + texture:SetHeight(11.5) + texture:Show() + texture:SetPoint("CENTER", colorSwatch, "CENTER") + colorSwatch:SetPoint("RIGHT", button, "RIGHT", 0, 0) + else + button = table.remove(buttons) + end + button:ClearAllPoints() + button:SetParent(level) + button:SetFrameStrata(level:GetFrameStrata()) + button:SetFrameLevel(level:GetFrameLevel() + 1) + button:SetPoint("LEFT", level, "LEFT", 10, 0) + button:SetPoint("RIGHT", level, "RIGHT", -10, 0) + if #level.buttons == 0 then + button:SetPoint("TOP", level, "TOP", 0, -10) + else + button:SetPoint("TOP", level.buttons[#level.buttons], "BOTTOM", 0, 0) + end + button.text:SetPoint("LEFT", button, "LEFT", 24, 0) + button:Show() + button.level = level + table.insert(level.buttons, button) + if not level.parented then + level.parented = true + level:ClearAllPoints() + if level.num == 1 then + if level.parent ~= UIParent then + level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT") + else + level:SetPoint("CENTER", level.parent, "CENTER") + end + else + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPLEFT", level.parent, "TOPRIGHT", 5, 10) + else + level:SetPoint("BOTTOMLEFT", level.parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT", -5, 10) + else + level:SetPoint("BOTTOMRIGHT", level.parent, "BOTTOMLEFT", -5, -10) + end + end + end + level:SetFrameStrata("FULLSCREEN_DIALOG") + end + button:SetAlpha(1) + return button +end + +local numLevels = 0 +local function AcquireLevel(self, level) + if not levels[level] then + for i = #levels + 1, level, -1 do + local i = i + numLevels = numLevels + 1 + local frame = CreateFrame("Button", "Dewdrop20Level" .. numLevels, nil) + if i == 1 then + local old_CloseSpecialWindows = CloseSpecialWindows + function CloseSpecialWindows() + local found = old_CloseSpecialWindows() + if levels[1]:IsShown() then + self:Close() + return 1 + end + return found + end + end + levels[i] = frame + frame.num = i + frame:SetParent(UIParent) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:Hide() + frame:SetWidth(180) + frame:SetHeight(10) + frame:SetFrameLevel(i * 3) + frame:SetScript("OnHide", function() + self:Close(level + 1) + end) + if frame.SetTopLevel then + frame:SetTopLevel(true) + end + frame:EnableMouse(true) + frame:EnableMouseWheel(true) + local backdrop = CreateFrame("Frame", nil, frame) + backdrop:SetAllPoints(frame) + backdrop:SetBackdrop(tmp( + 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'insets', tmp2( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ), + 'tileSize', 16, + 'edgeSize', 16 + )) + backdrop:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + backdrop:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + frame:SetScript("OnClick", function() + self:Close(i) + end) + frame:SetScript("OnEnter", function() + StopCounting(self, i) + end) + frame:SetScript("OnLeave", function() + StartCounting(self, i) + end) + frame:SetScript("OnMouseWheel", function() + Scroll(self, frame, arg1 < 0) + end) + if i == 1 then + frame:SetScript("OnUpdate", function(this, arg1) + OnUpdate(self, arg1) + end) + levels[1].lastDirection = "RIGHT" + levels[1].lastVDirection = "DOWN" + else + levels[i].lastDirection = levels[i - 1].lastDirection + levels[i].lastVDirection = levels[i - 1].lastVDirection + end + end + end + local fullscreenFrame = GetFullScreenFrame() + local l = levels[level] + local strata, framelevel = l:GetFrameStrata(), l:GetFrameLevel() + if fullscreenFrame then + l:SetParent(fullscreenFrame) + else + l:SetParent(UIParent) + end + l:SetFrameStrata(strata) + l:SetFrameLevel(framelevel) + l:SetAlpha(1) + return l +end + +local function validateOptions(options, position, baseOptions, fromPass) + if not baseOptions then + baseOptions = options + end + if type(options) ~= "table" then + return "Options must be a table.", position + end + local kind = options.type + if type(kind) ~= "string" then + return '"type" must be a string.', position + elseif kind ~= "group" and kind ~= "range" and kind ~= "text" and kind ~= "execute" and kind ~= "toggle" and kind ~= "color" and kind ~= "header" then + return '"type" must either be "range", "text", "group", "toggle", "execute", "color", or "header".', position + end + if options.aliases then + if type(options.aliases) ~= "table" and type(options.aliases) ~= "string" then + return '"alias" must be a table or string', position + end + end + if not fromPass then + if kind == "execute" then + if type(options.func) ~= "string" and type(options.func) ~= "function" then + return '"func" must be a string or function', position + end + elseif kind == "range" or kind == "text" or kind == "toggle" then + if type(options.set) ~= "string" and type(options.set) ~= "function" then + return '"set" must be a string or function', position + end + if kind == "text" and options.get == false then + elseif type(options.get) ~= "string" and type(options.get) ~= "function" then + return '"get" must be a string or function', position + end + elseif kind == "group" and options.pass then + if options.pass ~= true then + return '"pass" must be either nil, true, or false', position + end + if not options.func then + if type(options.set) ~= "string" and type(options.set) ~= "function" then + return '"set" must be a string or function', position + end + if type(options.get) ~= "string" and type(options.get) ~= "function" then + return '"get" must be a string or function', position + end + elseif type(options.func) ~= "string" and type(options.func) ~= "function" then + return '"func" must be a string or function', position + end + end + else + if kind == "group" then + return 'cannot have "type" = "group" as a subgroup of a passing group', position + end + end + if options ~= baseOptions then + if kind == "header" then + elseif type(options.desc) ~= "string" then + return '"desc" must be a string', position + elseif options.desc:len() == 0 then + return '"desc" cannot be a 0-length string', position + end + end + if options ~= baseOptions or kind == "range" or kind == "text" or kind == "toggle" or kind == "color" then + if options.type == "header" and not options.cmdName and not options.name then + elseif options.cmdName then + if type(options.cmdName) ~= "string" then + return '"cmdName" must be a string or nil', position + elseif options.cmdName:len() == 0 then + return '"cmdName" cannot be a 0-length string', position + end + if type(options.guiName) ~= "string" then + if not options.guiNameIsMap then + return '"guiName" must be a string or nil', position + end + elseif options.guiName:len() == 0 then + return '"guiName" cannot be a 0-length string', position + end + else + if type(options.name) ~= "string" then + return '"name" must be a string', position + elseif options.name:len() == 0 then + return '"name" cannot be a 0-length string', position + end + end + end + if options.guiNameIsMap then + if type(options.guiNameIsMap) ~= "boolean" then + return '"guiNameIsMap" must be a boolean or nil', position + elseif options.type ~= "toggle" then + return 'if "guiNameIsMap" is true, then "type" must be set to \'toggle\'', position + elseif type(options.map) ~= "table" then + return '"map" must be a table', position + end + end + if options.message and type(options.message) ~= "string" then + return '"message" must be a string or nil', position + end + if options.error and type(options.error) ~= "string" then + return '"error" must be a string or nil', position + end + if options.current and type(options.current) ~= "string" then + return '"current" must be a string or nil', position + end + if options.order then + if type(options.order) ~= "number" or (-1 < options.order and options.order < 0.999) then + return '"order" must be a non-zero number or nil', position + end + end + if options.disabled then + if type(options.disabled) ~= "function" and type(options.disabled) ~= "string" and options.disabled ~= true then + return '"disabled" must be a function, string, or boolean', position + end + end + if options.cmdHidden then + if type(options.cmdHidden) ~= "function" and type(options.cmdHidden) ~= "string" and options.cmdHidden ~= true then + return '"cmdHidden" must be a function, string, or boolean', position + end + end + if options.guiHidden then + if type(options.guiHidden) ~= "function" and type(options.guiHidden) ~= "string" and options.guiHidden ~= true then + return '"guiHidden" must be a function, string, or boolean', position + end + end + if options.hidden then + if type(options.hidden) ~= "function" and type(options.hidden) ~= "string" and options.hidden ~= true then + return '"hidden" must be a function, string, or boolean', position + end + end + if kind == "text" then + if type(options.validate) == "table" then + local t = options.validate + local iTable = nil + for k,v in pairs(t) do + if type(k) == "number" then + if iTable == nil then + iTable = true + elseif not iTable then + return '"validate" must either have all keys be indexed numbers or strings', position + elseif k < 1 or k > #t then + return '"validate" numeric keys must be indexed properly. >= 1 and <= #t', position + end + else + if iTable == nil then + iTable = false + elseif iTable then + return '"validate" must either have all keys be indexed numbers or strings', position + end + end + if type(v) ~= "string" then + return '"validate" values must all be strings', position + end + end + elseif options.validate == "keybinding" then + -- no other checks + else + if type(options.usage) ~= "string" then + return '"usage" must be a string', position + elseif options.validate and type(options.validate) ~= "string" and type(options.validate) ~= "function" then + return '"validate" must be a string, function, or table', position + end + end + elseif kind == "range" then + if options.min or options.max then + if type(options.min) ~= "number" then + return '"min" must be a number', position + elseif type(options.max) ~= "number" then + return '"max" must be a number', position + elseif options.min >= options.max then + return '"min" must be less than "max"', position + end + end + if options.step then + if type(options.step) ~= "number" then + return '"step" must be a number', position + elseif options.step < 0 then + return '"step" must be nonnegative', position + end + end + if options.isPercent and options.isPercent ~= true then + return '"isPercent" must either be nil, true, or false', position + end + elseif kind == "toggle" then + if options.map then + if type(options.map) ~= "table" then + return '"map" must be a table', position + elseif type(options.map[true]) ~= "string" then + return '"map[true]" must be a string', position + elseif type(options.map[false]) ~= "string" then + return '"map[false]" must be a string', position + end + end + elseif kind == "color" then + if options.hasAlpha and options.hasAlpha ~= true then + return '"hasAlpha" must be nil, true, or false', position + end + elseif kind == "group" then + if options.pass and options.pass ~= true then + return '"pass" must be nil, true, or false', position + end + if type(options.args) ~= "table" then + return '"args" must be a table', position + end + for k,v in pairs(options.args) do + if type(k) ~= "number" then + if type(k) ~= "string" then + return '"args" keys must be strings or numbers', position + elseif k:len() == 0 then + return '"args" keys must not be 0-length strings.', position + end + end + if type(v) ~= "table" then + return '"args" values must be tables', position and position .. "." .. k or k + end + local newposition + if position then + newposition = position .. ".args." .. k + else + newposition = "args." .. k + end + local err, pos = validateOptions(v, newposition, baseOptions, options.pass) + if err then + return err, pos + end + end + elseif kind == "execute" then + if type(options.confirm) ~= "string" and type(options.confirm) ~= "boolean" and type(options.confirm) ~= "nil" then + return '"confirm" must be a string, boolean, or nil', position + end + end + if options.icon and type(options.icon) ~= "string" then + return'"icon" must be a string', position + end + if options.iconWidth or options.iconHeight then + if type(options.iconWidth) ~= "number" or type(options.iconHeight) ~= "number" then + return '"iconHeight" and "iconWidth" must be numbers', position + end + end + if options.iconCoordLeft or options.iconCoordRight or options.iconCoordTop or options.iconCoordBottom then + if type(options.iconCoordLeft) ~= "number" or type(options.iconCoordRight) ~= "number" or type(options.iconCoordTop) ~= "number" or type(options.iconCoordBottom) ~= "number" then + return '"iconCoordLeft", "iconCoordRight", "iconCoordTop", and "iconCoordBottom" must be numbers', position + end + end +end + +local validatedOptions + +local values +local mysort_args +local mysort +local othersort +local othersort_validate + +local baseFunc, currentLevel + +local function confirmPopup(message, func, ...) + if not StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] then + StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] = {} + end + local t = StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] + for k in pairs(t) do + t[k] = nil + end + t.text = message + t.button1 = ACCEPT or "Accept" + t.button2 = CANCEL or "Cancel" + t.OnAccept = function() + func(unpack(t)) + end + for i = 1, select('#', ...) do + t[i] = select(i, ...) + end + t.timeout = 0 + t.whileDead = 1 + t.hideOnEscape = 1 + + Dewdrop:Close() + StaticPopup_Show("DEWDROP20_CONFIRM_DIALOG") +end + +function Dewdrop:FeedAceOptionsTable(options, difference) + self:argCheck(options, 2, "table") + self:argCheck(difference, 3, "nil", "number") + self:assert(currentLevel, "Cannot call `FeedAceOptionsTable' outside of a Dewdrop declaration") + if not difference then + difference = 0 + end + if not validatedOptions then + validatedOptions = {} + end + if not validatedOptions[options] then + local err, position = validateOptions(options) + + if err then + if position then + Dewdrop:error(position .. ": " .. err) + else + Dewdrop:error(err) + end + end + + validatedOptions[options] = true + end + local level = levels[currentLevel] + self:assert(level, "Improper level given") + if not values then + values = {} + else + for k,v in pairs(values) do + values[k] = nil + end + end + + local current = level + while current do + if current.num == difference + 1 then + break + end + table.insert(values, current.value) + current = levels[current.num - 1] + end + + local realOptions = options + local handler = options.handler + local passTable + local passValue + while #values > 0 do + passTable = options.pass and options or nil + local value = table.remove(values) + options = options.args and options.args[value] + if not options then + return + end + handler = options.handler or handler + passValue = passTable and value or nil + end + + if options.type == "group" then + local hidden, disabled = options.hidden + if hidden then + if type(hidden) == "function" then + hidden = hidden() + elseif type(hidden) == "string" then + local f = hidden + local neg = f:match("^~(.-)$") + if neg then + f = neg + end + hidden = handler[f](handler) + if neg then + hidden = not hidden + end + end + end + if hidden then + return + end + local disabled = options.disabled + if disabled then + if type(disabled) == "function" then + disabled = disabled() + elseif type(disabled) == "string" then + local f = disabled + local neg = f:match("^~(.-)$") + if neg then + f = neg + end + hidden = handler[f](handler) + if neg then + disabled = not disabled + end + end + end + if disabled then + self:AddLine( + 'text', DISABLED, + 'disabled', true + ) + return + end + for k in pairs(options.args) do + table.insert(values, k) + end + passTable = options.pass and options or nil + if not mysort then + mysort = function(a, b) + local alpha, bravo = mysort_args[a], mysort_args[b] + local alpha_order = alpha.order or 100 + local bravo_order = bravo.order or 100 + local alpha_name = alpha.guiName or alpha.name + local bravo_name = bravo.guiName or bravo.name + if alpha_order == bravo_order then + if not alpha_name then + return true + elseif not bravo_name then + return false + else + return alpha_name:upper() < bravo_name:upper() + end + else + if alpha_order < 0 then + if bravo_order > 0 then + return false + end + else + if bravo_order < 0 then + return true + end + end + return alpha_order < bravo_order + end + end + end + mysort_args = options.args + table.sort(values, mysort) + mysort_args = nil + local hasBoth = #values >= 1 and (options.args[values[1]].order or 100) > 0 and (options.args[values[#values]].order or 100) < 0 + local last_order = 1 + for _,k in ipairs(values) do + local v = options.args[k] + local handler = v.handler or handler + if hasBoth and last_order > 0 and (v.order or 100) < 0 then + hasBoth = false + self:AddLine() + end + local hidden, disabled = v.guiHidden or v.hidden, v.disabled + if type(hidden) == "function" then + hidden = hidden() + elseif type(hidden) == "string" then + local f = hidden + local neg = f:match("^~(.-)$") + if neg then + f = neg + end + hidden = handler[f](handler) + if neg then + hidden = not hidden + end + end + if not hidden then + if type(disabled) == "function" then + disabled = disabled() + elseif type(disabled) == "string" then + local f = disabled + local neg = f:match("^~(.-)$") + if neg then + f = neg + end + disabled = handler[f](handler) + if neg then + disabled = not disabled + end + end + local name = (v.guiIconOnly and v.icon) and "" or (v.guiName or v.name) + local desc = v.desc + local iconHeight = v.iconHeight or 16 + local iconWidth = v.iconWidth or 16 + local iconCoordLeft = v.iconCoordLeft + local iconCoordRight = v.iconCoordRight + local iconCoordBottom = v.iconCoordBottom + local iconCoordTop = v.iconCoordTop + local tooltipTitle, tooltipText + tooltipTitle = name + if name ~= desc then + tooltipText = desc + end + local v_p = passTable or v + local passValue = passTable and k or nil + if v.type == "toggle" then + local checked + local checked_arg + if type(v_p.get) == "function" then + checked = v_p.get(passValue) + checked_arg = checked + else + local f = v_p.get + local neg = f:match("^~(.-)$") + if neg then + f = neg + end + if not handler[f] then + Dewdrop:error("Handler %q not available", f) + end + checked = handler[f](handler, passValue) + checked_arg = checked + if neg then + checked = not checked + end + end + local func, arg1, arg2, arg3 + if type(v_p.set) == "function" then + func = v_p.set + if passValue then + arg1 = passValue + arg2 = not checked_arg + else + arg1 = not checked_arg + end + else + if not handler[v_p.set] then + Dewdrop:error("Handler %q not available", v_p.set) + end + func = handler[v_p.set] + arg1 = handler + if passValue then + arg2 = passValue + arg3 = not checked_arg + else + arg2 = not checked_arg + end + end + if v.guiNameIsMap then + checked = checked and true or false + name = tostring(v.map and v.map[checked]):gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") + tooltipTitle = name + checked = true--nil + end + self:AddLine( + 'text', name, + 'checked', checked, + 'isRadio', v.isRadio, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'arg3', arg3, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + elseif v.type == "execute" then + local func, arg1, arg2, arg3, arg4 + local confirm = v.confirm + if confirm == true then + confirm = DEFAULT_CONFIRM_MESSAGE:format(tooltipText or tooltipTitle) + end + if type(v_p.func) == "function" then + if confirm then + func = confirmPopup + arg1 = confirm + arg2 = v_p.func + arg3 = passValue + else + func = v_p.func + arg1 = passValue + end + else + if not handler[v_p.func] then + Dewdrop:error("Handler %q not available", v_p.func) + end + if confirm then + func = confirmPopup + arg1 = confirm + arg2 = handler[v_p.func] + arg3 = handler + arg4 = passValue + else + func = handler[v_p.func] + arg1 = handler + arg2 = passValue + end + end + self:AddLine( + 'text', name, + 'checked', checked, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'arg3', arg3, + 'arg4', arg4, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + elseif v.type == "range" then + local sliderValue + if type(v_p.get) == "function" then + sliderValue = v_p.get(passValue) + else + if not handler[v_p.get] then + Dewdrop:error("Handler %q not available", v_p.get) + end + sliderValue = handler[v_p.get](handler, passValue) + end + local sliderFunc, sliderArg1, sliderArg2 + if type(v_p.set) == "function" then + sliderFunc = v_p.set + sliderArg1 = passValue + else + if not handler[v_p.set] then + Dewdrop:error("Handler %q not available", v_p.set) + end + sliderFunc = handler[v_p.set] + sliderArg1 = handler + sliderArg2 = passValue + end + self:AddLine( + 'text', name, + 'hasArrow', true, + 'hasSlider', true, + 'sliderMin', v.min or 0, + 'sliderMax', v.max or 1, + 'sliderStep', v.step or 0, + 'sliderIsPercent', v.isPercent or false, + 'sliderValue', sliderValue, + 'sliderFunc', sliderFunc, + 'sliderArg1', sliderArg1, + 'sliderArg2', sliderArg2, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + elseif v.type == "color" then + local r,g,b,a + if type(v_p.get) == "function" then + r,g,b,a = v_p.get(passValue) + else + if not handler[v_p.get] then + Dewdrop:error("Handler %q not available", v_p.get) + end + r,g,b,a = handler[v_p.get](handler, passValue) + end + local colorFunc, colorArg1, colorArg2 + if type(v_p.set) == "function" then + colorFunc = v_p.set + colorArg1 = passValue + else + if not handler[v_p.set] then + Dewdrop:error("Handler %q not available", v_p.set) + end + colorFunc = handler[v_p.set] + colorArg1 = handler + colorArg2 = passValue + end + self:AddLine( + 'text', name, + 'hasArrow', true, + 'hasColorSwatch', true, + 'r', r, + 'g', g, + 'b', b, + 'opacity', v.hasAlpha and a or nil, + 'hasOpacity', v.hasAlpha, + 'colorFunc', colorFunc, + 'colorArg1', colorArg1, + 'colorArg2', colorArg2, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + elseif v.type == "text" then + if type(v.validate) == "table" then + self:AddLine( + 'text', name, + 'hasArrow', true, + 'value', k, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + else + local editBoxText + if type(v_p.get) == "function" then + editBoxText = v_p.get(passValue) + elseif v_p.get == false then + editBoxText = nil + else + if not handler[v_p.get] then + Dewdrop:error("Handler %q not available", v_p.get) + end + editBoxText = handler[v_p.get](handler, passValue) + end + local editBoxFunc, editBoxArg1, editBoxArg2 + if type(v_p.set) == "function" then + editBoxFunc = v_p.set + editBoxArg1 = passValue + else + if not handler[v_p.set] then + Dewdrop:error("Handler %q not available", v_p.set) + end + editBoxFunc = handler[v_p.set] + editBoxArg1 = handler + editBoxArg2 = passValue + end + + local editBoxValidateFunc, editBoxValidateArg1 + + if v.validate and v.validate ~= "keybinding" then + if type(v.validate) == "function" then + editBoxValidateFunc = v.validate + else + if not handler[v.validate] then + Dewdrop:error("Handler %q not available", v.validate) + end + editBoxValidateFunc = handler[v.validate] + editBoxValidateArg1 = handler + end + elseif v.validate then + if tooltipText then + tooltipText = tooltipText .. "\n\n" .. RESET_KEYBINDING_DESC + else + tooltipText = RESET_KEYBINDING_DESC + end + end + + self:AddLine( + 'text', name, + 'hasArrow', true, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom, + 'hasEditBox', true, + 'editBoxText', editBoxText, + 'editBoxFunc', editBoxFunc, + 'editBoxArg1', editBoxArg1, + 'editBoxArg2', editBoxArg2, + 'editBoxValidateFunc', editBoxValidateFunc, + 'editBoxValidateArg1', editBoxValidateArg1, + 'editBoxIsKeybinding', v.validate == "keybinding", + 'editBoxKeybindingOnly', v.keybindingOnly, + 'editBoxKeybindingExcept', v.keybindingExcept, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + end + elseif v.type == "group" then + self:AddLine( + 'text', name, + 'hasArrow', true, + 'value', k, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + elseif v.type == "header" then + if name == "" or not name then + self:AddLine( + 'isTitle', true, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + else + self:AddLine( + 'text', name, + 'isTitle', true, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + end + end + end + last_order = v.order or 100 + end + elseif options.type == "text" and type(options.validate) == "table" then + local current + local options_p = passTable or options + if type(options_p.get) == "function" then + current = options_p.get(passValue) + elseif options_p.get ~= false then + if not handler[options_p.get] then + Dewdrop:error("Handler %q not available", options_p.get) + end + current = handler[options_p.get](handler, passValue) + end + local indexed = true + for k,v in pairs(options.validate) do + if type(k) ~= "number" then + indexed = false + end + table.insert(values, k) + end + if not indexed then + if not othersort then + othersort = function(alpha, bravo) + return othersort_validate[alpha] < othersort_validate[bravo] + end + end + othersort_validate = options.validate + table.sort(values, othersort) + othersort_validate = nil + end + for _,k in ipairs(values) do + local v = options.validate[k] + if type(k) == "number" then + k = v + end + local func, arg1, arg2, arg3 + if type(options_p.set) == "function" then + func = options_p.set + if passValue then + arg1 = passValue + arg2 = k + else + arg1 = k + end + else + if not handler[options.set] then + Dewdrop:error("Handler %q not available", options.set) + end + func = handler[options.set] + arg1 = handler + if passValue then + arg2 = passValue + arg3 = k + else + arg2 = k + end + end + local checked = (k == current or (type(k) == "string" and type(current) == "string" and k:lower() == current:lower())) + self:AddLine( + 'text', v, + 'func', not checked and func or nil, + 'arg1', not checked and arg1 or nil, + 'arg2', not checked and arg2 or nil, + 'arg3', not checked and arg3 or nil, + 'isRadio', true, + 'checked', checked, + 'tooltipTitle', options.guiName or options.name, + 'tooltipText', v + ) + end + for k in pairs(values) do + values[k] = nil + end + else + return false + end + return true +end + +function Refresh(self, level) + if type(level) == "number" then + level = levels[level] + end + if not level then + return + end + if baseFunc then + Clear(self, level) + currentLevel = level.num + if type(baseFunc) == "table" then + if currentLevel == 1 then + local handler = baseFunc.handler + if handler then + local name = tostring(handler) + if not name:find('^table:') and not handler.hideMenuTitle then + name = name:gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") + self:AddLine( + 'text', name, + 'isTitle', true + ) + end + end +-- elseif level.parentText then +-- self:AddLine( +-- 'text', level.parentText, +-- 'tooltipTitle', level.parentTooltipTitle, +-- 'tooltipText', level.parentTooltipText, +-- 'tooltipFunc', level.parentTooltipFunc, +-- 'isTitle', true +-- ) + end + self:FeedAceOptionsTable(baseFunc) + if currentLevel == 1 then + self:AddLine( + 'text', CLOSE, + 'tooltipTitle', CLOSE, + 'tooltipText', CLOSE_DESC, + 'closeWhenClicked', true + ) + end + else +-- if level.parentText then +-- self:AddLine( +-- 'text', level.parentText, +-- 'tooltipTitle', level.parentTooltipTitle, +-- 'tooltipText', level.parentTooltipText, +-- 'tooltipFunc', level.parentTooltipFunc, +-- 'isTitle', true +-- ) +-- end + baseFunc(currentLevel, level.value, levels[level.num - 1] and levels[level.num - 1].value, levels[level.num - 2] and levels[level.num - 2].value, levels[level.num - 3] and levels[level.num - 3].value, levels[level.num - 4] and levels[level.num - 4].value) + end + currentLevel = nil + CheckSize(self, level) + end +end + +function Dewdrop:Refresh(level) + self:argCheck(level, 2, "number") + Refresh(self, levels[level]) +end + +function OpenSlider(self, parent) + if not sliderFrame then + sliderFrame = CreateFrame("Frame", nil, nil) + sliderFrame:SetWidth(80) + sliderFrame:SetHeight(170) + sliderFrame:SetScale(UIParent:GetScale()) + sliderFrame:SetBackdrop(tmp( + 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'insets', tmp2( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ), + 'tileSize', 16, + 'edgeSize', 16 + )) + sliderFrame:SetFrameStrata("FULLSCREEN_DIALOG") + if sliderFrame.SetTopLevel then + sliderFrame:SetTopLevel(true) + end + sliderFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + sliderFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + sliderFrame:EnableMouse(true) + sliderFrame:Hide() + sliderFrame:SetPoint("CENTER", UIParent, "CENTER") + local slider = CreateFrame("Slider", nil, sliderFrame) + sliderFrame.slider = slider + slider:SetOrientation("VERTICAL") + slider:SetMinMaxValues(0, 1) + slider:SetValueStep(0.01) + slider:SetValue(0.5) + slider:SetWidth(16) + slider:SetHeight(128) + slider:SetPoint("LEFT", sliderFrame, "LEFT", 15, 0) + slider:SetBackdrop(tmp( + 'bgFile', "Interface\\Buttons\\UI-SliderBar-Background", + 'edgeFile', "Interface\\Buttons\\UI-SliderBar-Border", + 'tile', true, + 'edgeSize', 8, + 'tileSize', 8, + 'insets', tmp2( + 'left', 3, + 'right', 3, + 'top', 3, + 'bottom', 3 + ) + )) + local texture = slider:CreateTexture() + slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical") + local text = slider:CreateFontString(nil, "ARTWORK") + sliderFrame.topText = text + text:SetFontObject(GameFontGreenSmall) + text:SetText("100%") + text:SetPoint("BOTTOM", slider, "TOP") + local text = slider:CreateFontString(nil, "ARTWORK") + sliderFrame.bottomText = text + text:SetFontObject(GameFontGreenSmall) + text:SetText("0%") + text:SetPoint("TOP", slider, "BOTTOM") + local text = slider:CreateFontString(nil, "ARTWORK") + sliderFrame.currentText = text + text:SetFontObject(GameFontHighlightSmall) + text:SetText("50%") + text:SetPoint("LEFT", slider, "RIGHT") + text:SetPoint("RIGHT", sliderFrame, "RIGHT", -6, 0) + text:SetJustifyH("CENTER") + local changed = false + local inside = false + slider:SetScript("OnValueChanged", function() + if sliderFrame.changing then + return + end + changed = true + local done = false + if sliderFrame.parent and sliderFrame.parent.sliderFunc then + local min = sliderFrame.parent.sliderMin or 0 + local max = sliderFrame.parent.sliderMax or 1 + local step = sliderFrame.parent.sliderStep or (max - min) / 100 + local value = (1 - slider:GetValue()) * (max - min) + min + if step > 0 then + value = math.floor((value - min) / step + 0.5) * step + min + if value > max then + value = max + elseif value < min then + value = min + end + end + if value == sliderFrame.lastValue then + return + end + sliderFrame.lastValue = value + local text = sliderFrame.parent.sliderFunc(getArgs(sliderFrame.parent, 'sliderArg', 1, value)) + if text then + sliderFrame.currentText:SetText(text) + done = true + end + end + if not done then + local min = sliderFrame.parent.sliderMin or 0 + local max = sliderFrame.parent.sliderMax or 1 + local step = sliderFrame.parent.sliderStep or (max - min) / 100 + local value = (1 - slider:GetValue()) * (max - min) + min + if step > 0 then + value = math.floor((value - min) / step + 0.5) * step + min + if value > max then + value = max + elseif value < min then + value = min + end + end + if sliderFrame.parent.sliderIsPercent then + sliderFrame.currentText:SetText(string.format("%.0f%%", value * 100)) + else + if step < 0.1 then + sliderFrame.currentText:SetText(string.format("%.2f", value)) + elseif step < 1 then + sliderFrame.currentText:SetText(string.format("%.1f", value)) + else + sliderFrame.currentText:SetText(string.format("%.0f", value)) + end + end + end + end) + sliderFrame:SetScript("OnEnter", function() + StopCounting(self, sliderFrame.level) + showGameTooltip(sliderFrame.parent) + end) + sliderFrame:SetScript("OnLeave", function() + StartCounting(self, sliderFrame.level) + GameTooltip:Hide() + end) + slider:SetScript("OnMouseDown", function() + sliderFrame.mouseDown = true + GameTooltip:Hide() + end) + slider:SetScript("OnMouseUp", function() + sliderFrame.mouseDown = false + if changed--[[ and not inside]] then + local parent = sliderFrame.parent + local sliderFunc = parent.sliderFunc + for i = 1, sliderFrame.level - 1 do + Refresh(self, levels[i]) + end + local newParent + for _,button in ipairs(levels[sliderFrame.level-1].buttons) do + if button.sliderFunc == sliderFunc then + newParent = button + break + end + end + if newParent then + OpenSlider(self, newParent) + else + sliderFrame:Hide() + end + end + if inside then + showGameTooltip(sliderFrame.parent) + end + end) + slider:SetScript("OnEnter", function() + inside = true + StopCounting(self, sliderFrame.level) + showGameTooltip(sliderFrame.parent) + end) + slider:SetScript("OnLeave", function() + inside = false + StartCounting(self, sliderFrame.level) + GameTooltip:Hide() + if changed and not sliderFrame.mouseDown then + local parent = sliderFrame.parent + local sliderFunc = parent.sliderFunc + for i = 1, sliderFrame.level - 1 do + Refresh(self, levels[i]) + end + local newParent + for _,button in ipairs(levels[sliderFrame.level-1].buttons) do + if button.sliderFunc == sliderFunc then + newParent = button + break + end + end + if newParent then + OpenSlider(self, newParent) + else + sliderFrame:Hide() + end + end + end) + end + sliderFrame.parent = parent + sliderFrame.level = parent.level.num + 1 + sliderFrame.parentValue = parent.level.value + sliderFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3) + sliderFrame.slider:SetFrameLevel(sliderFrame:GetFrameLevel() + 1) + sliderFrame.changing = true + if not parent.sliderMin or not parent.sliderMax then + return + end + + if parent.arrow then +-- parent.arrow:SetVertexColor(0.2, 0.6, 0) +-- parent.arrow:SetHeight(24) +-- parent.arrow:SetWidth(24) + parent.selected = true + parent.highlight:Show() + end + + sliderFrame:SetClampedToScreen(false) + if not parent.sliderValue then + parent.sliderValue = (parent.sliderMin + parent.sliderMax) / 2 + end + sliderFrame.slider:SetValue(1 - (parent.sliderValue - parent.sliderMin) / (parent.sliderMax - parent.sliderMin)) + sliderFrame.changing = false + sliderFrame.bottomText:SetText(parent.sliderMinText or "0") + sliderFrame.topText:SetText(parent.sliderMaxText or "1") + local text + if parent.sliderFunc then + text = parent.sliderFunc(getArgs(parent, 'sliderArg', 1, parent.sliderValue)) + end + if text then + sliderFrame.currentText:SetText(text) + elseif parent.sliderIsPercent then + sliderFrame.currentText:SetText(string.format("%.0f%%", parent.sliderValue * 100)) + else + sliderFrame.currentText:SetText(parent.sliderValue) + end + + sliderFrame.lastValue = parent.sliderValue + + local level = parent.level + sliderFrame:Show() + sliderFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + local dirty + if level.lastDirection == "RIGHT" then + if sliderFrame:GetRight() > GetScreenWidth() then + level.lastDirection = "LEFT" + dirty = true + end + elseif sliderFrame:GetLeft() < 0 then + level.lastDirection = "RIGHT" + dirty = true + end + if level.lastVDirection == "DOWN" then + if sliderFrame:GetBottom() < 0 then + level.lastVDirection = "UP" + dirty = true + end + elseif sliderFrame:GetTop() > GetScreenWidth() then + level.lastVDirection = "DOWN" + dirty = true + end + if dirty then + sliderFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + end + local left, bottom = sliderFrame:GetLeft(), sliderFrame:GetBottom() + sliderFrame:ClearAllPoints() + sliderFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + if mod(level.num, 5) == 0 then + local left, bottom = level:GetLeft(), level:GetBottom() + level:ClearAllPoints() + level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + end + sliderFrame:SetClampedToScreen(true) +end + +function OpenEditBox(self, parent) + if not editBoxFrame then + editBoxFrame = CreateFrame("Frame", nil, nil) + editBoxFrame:SetWidth(200) + editBoxFrame:SetHeight(40) + editBoxFrame:SetScale(UIParent:GetScale()) + editBoxFrame:SetBackdrop(tmp( + 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'insets', tmp2( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ), + 'tileSize', 16, + 'edgeSize', 16 + )) + editBoxFrame:SetFrameStrata("FULLSCREEN_DIALOG") + if editBoxFrame.SetTopLevel then + editBoxFrame:SetTopLevel(true) + end + editBoxFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + editBoxFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + editBoxFrame:EnableMouse(true) + editBoxFrame:EnableMouseWheel(true) + editBoxFrame:Hide() + editBoxFrame:SetPoint("CENTER", UIParent, "CENTER") + + local editBox = CreateFrame("EditBox", nil, editBoxFrame) + editBoxFrame.editBox = editBox + editBox:SetFontObject(ChatFontNormal) + editBox:SetWidth(160) + editBox:SetHeight(13) + editBox:SetPoint("CENTER", editBoxFrame, "CENTER", 0, 0) + + local left = editBox:CreateTexture(nil, "BACKGROUND") + left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left") + left:SetTexCoord(0, 100 / 256, 0, 1) + left:SetWidth(100) + left:SetHeight(32) + left:SetPoint("LEFT", editBox, "LEFT", -10, 0) + local right = editBox:CreateTexture(nil, "BACKGROUND") + right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right") + right:SetTexCoord(156/256, 1, 0, 1) + right:SetWidth(100) + right:SetHeight(32) + right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0) + + editBox:SetScript("OnEnterPressed", function() + if editBoxFrame.parent and editBoxFrame.parent.editBoxValidateFunc then + local t = editBox.realText or editBox:GetText() or "" + local result = editBoxFrame.parent.editBoxValidateFunc(getArgs(editBoxFrame.parent, 'editBoxValidateArg', 1, t)) + if not result then + UIErrorsFrame:AddMessage(VALIDATION_ERROR, 1, 0, 0) + return + end + end + if editBoxFrame.parent and editBoxFrame.parent.editBoxFunc then + local t + if editBox.realText ~= "NONE" then + t = editBox.realText or editBox:GetText() or "" + end + editBoxFrame.parent.editBoxFunc(getArgs(editBoxFrame.parent, 'editBoxArg', 1, t)) + end + self:Close(editBoxFrame.level) + for i = 1, editBoxFrame.level - 1 do + Refresh(self, levels[i]) + end + StartCounting(self, editBoxFrame.level-1) + end) + editBox:SetScript("OnEscapePressed", function() + self:Close(editBoxFrame.level) + StartCounting(self, editBoxFrame.level-1) + end) + editBox:SetScript("OnReceiveDrag", function(this) + if GetCursorInfo then + local type, alpha, bravo = GetCursorInfo() + local text + if type == "spell" then + text = GetSpellName(alpha, bravo) + elseif type == "item" then + text = bravo + end + if not text then + return + end + ClearCursor() + editBox:SetText(text) + end + end) + local changing = false + local skipNext = false + + function editBox:SpecialSetText(text) + local oldText = editBox:GetText() or "" + if not text then + text = "" + end + if text ~= oldText then + changing = true + self:SetText(text) + changing = false + skipNext = true + end + end + + editBox:SetScript("OnTextChanged", function() + if skipNext then + skipNext = false + elseif not changing and editBoxFrame.parent and editBoxFrame.parent.editBoxChangeFunc then + local t + if editBox.realText ~= "NONE" then + t = editBox.realText or editBox:GetText() or "" + end + local text = editBoxFrame.parent.editBoxChangeFunc(getArgs(editBoxFrame.parent, 'editBoxChangeArg', 1, t)) + if text then + editBox:SpecialSetText(text) + end + end + end) + editBoxFrame:SetScript("OnEnter", function() + StopCounting(self, editBoxFrame.level) + showGameTooltip(editBoxFrame.parent) + end) + editBoxFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + editBox:SetScript("OnEnter", function() + StopCounting(self, editBoxFrame.level) + showGameTooltip(editBoxFrame.parent) + end) + editBox:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + editBoxFrame:SetScript("OnKeyDown", function(this, a1) + if not editBox.keybinding then + return + end + local arg1 = a1 or arg1 + local screenshotKey = GetBindingKey("SCREENSHOT") + if screenshotKey and arg1 == screenshotKey then + Screenshot() + return + end + + if arg1 == "LeftButton" then + arg1 = "BUTTON1" + elseif arg1 == "RightButton" then + arg1 = "BUTTON2" + elseif arg1 == "MiddleButton" then + arg1 = "BUTTON3" + elseif arg1 == "Button4" then + arg1 = "BUTTON4" + elseif arg1 == "Button5" then + arg1 = "BUTTON5" + end + if arg1 == "UNKNOWN" then + return + elseif arg1 == "SHIFT" or arg1 == "CTRL" or arg1 == "ALT" then + return + elseif arg1 == "ENTER" then + if editBox.keybindingOnly and not editBox.keybindingOnly[editBox.realText] then + return editBox:GetScript("OnEscapePressed")() + elseif editBox.keybindingExcept and editBox.keybindingExcept[editBox.realText] then + return editBox:GetScript("OnEscapePressed")() + else + return editBox:GetScript("OnEnterPressed")() + end + elseif arg1 == "ESCAPE" then + if editBox.realText == "NONE" then + return editBox:GetScript("OnEscapePressed")() + else + editBox:SpecialSetText(NONE or "NONE") + editBox.realText = "NONE" + return + end + elseif editBox.keybindingOnly and not editBox.keybindingOnly[arg1] then + return + elseif editBox.keybindingExcept and editBox.keybindingExcept[arg1] then + return + end + local s = GetBindingText(arg1, "KEY_") + if s == "BUTTON1" then + s = KEY_BUTTON1 + elseif s == "BUTTON2" then + s = KEY_BUTTON2 + end + local real = arg1 + if IsShiftKeyDown() then + s = "Shift-" .. s + real = "SHIFT-" .. real + end + if IsControlKeyDown() then + s = "Ctrl-" .. s + real = "CTRL-" .. real + end + if IsAltKeyDown() then + s = "Alt-" .. s + real = "ALT-" .. real + end + if editBox:GetText() ~= s then + editBox:SpecialSetText("-") + editBox:SpecialSetText(s) + editBox.realText = real + return editBox:GetScript("OnTextChanged")() + end + end) + editBoxFrame:SetScript("OnMouseDown", editBoxFrame:GetScript("OnKeyDown")) + editBox:SetScript("OnMouseDown", function(this, ...) + if GetCursorInfo and (CursorHasItem() or CursorHasSpell()) then + return editBox:GetScript("OnReceiveDrag")(this, ...) + end + return editBoxFrame:GetScript("OnKeyDown")(this, ...) + end) + editBoxFrame:SetScript("OnMouseWheel", function(t, a1) + local arg1 = a1 or arg1 + local up = arg1 > 0 + arg1 = up and "MOUSEWHEELUP" or "MOUSEWHEELDOWN" + return editBoxFrame:GetScript("OnKeyDown")(t or this, arg1) + end) + editBox:SetScript("OnMouseWheel", editBoxFrame:GetScript("OnMouseWheel")) + end + editBoxFrame.parent = parent + editBoxFrame.level = parent.level.num + 1 + editBoxFrame.parentValue = parent.level.value + editBoxFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3) + editBoxFrame.editBox:SetFrameLevel(editBoxFrame:GetFrameLevel() + 1) + editBoxFrame.editBox.realText = nil + editBoxFrame:SetClampedToScreen(false) + + editBoxFrame.editBox:SpecialSetText("") + if parent.editBoxIsKeybinding then + local s = parent.editBoxText + if s == "" then + s = "NONE" + end + editBoxFrame.editBox.realText = s + if s and s ~= "NONE" then + local alpha,bravo = s:match("^(.+)%-(.+)$") + if not bravo then + alpha = nil + bravo = s + end + bravo = GetBindingText(bravo, "KEY_") + if alpha then + editBoxFrame.editBox:SpecialSetText(alpha:upper() .. "-" .. bravo) + else + editBoxFrame.editBox:SpecialSetText(bravo) + end + else + editBoxFrame.editBox:SpecialSetText(NONE or "NONE") + end + else + editBoxFrame.editBox:SpecialSetText(parent.editBoxText) + end + + editBoxFrame.editBox.keybinding = parent.editBoxIsKeybinding + editBoxFrame.editBox.keybindingOnly = parent.editBoxKeybindingOnly + editBoxFrame.editBox.keybindingExcept = parent.editBoxKeybindingExcept + editBoxFrame.editBox:EnableKeyboard(not parent.editBoxIsKeybinding) + editBoxFrame:EnableKeyboard(parent.editBoxIsKeybinding) + + if parent.arrow then +-- parent.arrow:SetVertexColor(0.2, 0.6, 0) +-- parent.arrow:SetHeight(24) +-- parent.arrow:SetWidth(24) + parent.selected = true + parent.highlight:Show() + end + + local level = parent.level + editBoxFrame:Show() + editBoxFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + local dirty + if level.lastDirection == "RIGHT" then + if editBoxFrame:GetRight() > GetScreenWidth() then + level.lastDirection = "LEFT" + dirty = true + end + elseif editBoxFrame:GetLeft() < 0 then + level.lastDirection = "RIGHT" + dirty = true + end + if level.lastVDirection == "DOWN" then + if editBoxFrame:GetBottom() < 0 then + level.lastVDirection = "UP" + dirty = true + end + elseif editBoxFrame:GetTop() > GetScreenWidth() then + level.lastVDirection = "DOWN" + dirty = true + end + if dirty then + editBoxFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + end + local left, bottom = editBoxFrame:GetLeft(), editBoxFrame:GetBottom() + editBoxFrame:ClearAllPoints() + editBoxFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + if mod(level.num, 5) == 0 then + local left, bottom = level:GetLeft(), level:GetBottom() + level:ClearAllPoints() + level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + end + editBoxFrame:SetClampedToScreen(true) +end + +function Dewdrop:EncodeKeybinding(text) + if text == nil or text == "NONE" then + return nil + end + text = tostring(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 not text:find("^F%d+$") and text ~= "CAPSLOCK" and text:len() ~= 1 and (text:len() == 0 or text:byte() < 128 or text:len() > 4) and not _G["KEY_" .. text] and text ~= "BUTTON1" and text ~= "BUTTON2" then + return false + end + local s = GetBindingText(text, "KEY_") + if s == "BUTTON1" then + s = KEY_BUTTON1 + elseif s == "BUTTON2" then + s = KEY_BUTTON2 + end + if shift then + s = "Shift-" .. s + end + if ctrl then + s = "Ctrl-" .. s + end + if alt then + s = "Alt-" .. s + end + return s +end + +function Dewdrop:IsOpen(parent) + self:argCheck(parent, 2, "table", "nil") + return levels[1] and levels[1]:IsShown() and (not parent or parent == levels[1].parent or parent == levels[1]:GetParent()) +end + +function Dewdrop:GetOpenedParent() + return (levels[1] and levels[1]:IsShown()) and (levels[1].parent or levels[1]:GetParent()) +end + +function Open(self, parent, func, level, value, point, relativePoint, cursorX, cursorY) + self:Close(level) + if DewdropLib then + local d = DewdropLib:GetInstance('1.0') + local ret, val = pcall(d, IsOpen, d) + if ret and val then + DewdropLib:GetInstance('1.0'):Close() + end + end + parent:GetCenter() + local frame = AcquireLevel(self, level) + if level == 1 then + frame.lastDirection = "RIGHT" + frame.lastVDirection = "DOWN" + else + frame.lastDirection = levels[level - 1].lastDirection + frame.lastVDirection = levels[level - 1].lastVDirection + end + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:ClearAllPoints() + frame.parent = parent + frame:SetPoint("LEFT", UIParent, "RIGHT", 10000, 0) + frame:Show() + if level == 1 then + baseFunc = func + end + levels[level].value = value +-- levels[level].parentText = parent.text and parent.text:GetText() or nil +-- levels[level].parentTooltipTitle = parent.tooltipTitle +-- levels[level].parentTooltipText = parent.tooltipText +-- levels[level].parentTooltipFunc = parent.tooltipFunc + if parent.arrow then +-- parent.arrow:SetVertexColor(0.2, 0.6, 0) +-- parent.arrow:SetHeight(24) +-- parent.arrow:SetWidth(24) + parent.selected = true + parent.highlight:Show() + end + relativePoint = relativePoint or point + Refresh(self, levels[level]) + if point or (cursorX and cursorY) then + frame:ClearAllPoints() + if cursorX and cursorY then + local curX, curY = GetScaledCursorPosition() + if curY < GetScreenHeight() / 2 then + point, relativePoint = "BOTTOM", "BOTTOM" + else + point, relativePoint = "TOP", "TOP" + end + if curX < GetScreenWidth() / 2 then + point, relativePoint = point .. "LEFT", relativePoint .. "RIGHT" + else + point, relativePoint = point .. "RIGHT", relativePoint .. "LEFT" + end + end + frame:SetPoint(point, parent, relativePoint) + if cursorX and cursorY then + local left = frame:GetLeft() + local width = frame:GetWidth() + local bottom = frame:GetBottom() + local height = frame:GetHeight() + local curX, curY = GetScaledCursorPosition() + frame:ClearAllPoints() + relativePoint = relativePoint or point + if point == "BOTTOM" or point == "TOP" then + if curX < GetScreenWidth() / 2 then + point = point .. "LEFT" + else + point = point .. "RIGHT" + end + elseif point == "CENTER" then + if curX < GetScreenWidth() / 2 then + point = "LEFT" + else + point = "RIGHT" + end + end + local xOffset, yOffset = 0, 0 + if curY > GetScreenHeight() / 2 then + yOffset = -height + end + if curX > GetScreenWidth() / 2 then + xOffset = -width + end + frame:SetPoint(point, parent, relativePoint, curX - left + xOffset, curY - bottom + yOffset) + if level == 1 then + frame.lastDirection = "RIGHT" + end + elseif cursorX then + local left = frame:GetLeft() + local width = frame:GetWidth() + local curX, curY = GetScaledCursorPosition() + frame:ClearAllPoints() + relativePoint = relativePoint or point + if point == "BOTTOM" or point == "TOP" then + if curX < GetScreenWidth() / 2 then + point = point .. "LEFT" + else + point = point .. "RIGHT" + end + elseif point == "CENTER" then + if curX < GetScreenWidth() / 2 then + point = "LEFT" + else + point = "RIGHT" + end + end + frame:SetPoint(point, parent, relativePoint, curX - left - width / 2, 0) + if level == 1 then + frame.lastDirection = "RIGHT" + end + elseif cursorY then + local bottom = frame:GetBottom() + local height = frame:GetHeight() + local curX, curY = GetScaledCursorPosition() + frame:ClearAllPoints() + relativePoint = relativePoint or point + if point == "LEFT" or point == "RIGHT" then + if curX < GetScreenHeight() / 2 then + point = point .. "BOTTOM" + else + point = point .. "TOP" + end + elseif point == "CENTER" then + if curX < GetScreenHeight() / 2 then + point = "BOTTOM" + else + point = "TOP" + end + end + frame:SetPoint(point, parent, relativePoint, 0, curY - bottom - height / 2) + if level == 1 then + frame.lastDirection = "DOWN" + end + end + if (strsub(point, 1, 3) ~= strsub(relativePoint, 1, 3)) then + if frame:GetBottom() < 0 then + local point, parent, relativePoint, x, y = frame:GetPoint(1) + local change = GetScreenHeight() - frame:GetTop() + local otherChange = -frame:GetBottom() + if otherChange < change then + change = otherChange + end + frame:SetPoint(point, parent, relativePoint, x, y + change) + elseif frame:GetTop() > GetScreenHeight() then + local point, parent, relativePoint, x, y = frame:GetPoint(1) + local change = GetScreenHeight() - frame:GetTop() + local otherChange = -frame:GetBottom() + if otherChange < change then + change = otherChange + end + frame:SetPoint(point, parent, relativePoint, x, y + change) + end + end + end + CheckDualMonitor(self, frame) + frame:SetClampedToScreen(true) + frame:SetClampedToScreen(false) + StartCounting(self, level) +end + +function Dewdrop:IsRegistered(parent) + self:argCheck(parent, 2, "table") + return not not self.registry[parent] +end + +function Dewdrop:Register(parent, ...) + self:argCheck(parent, 2, "table") + if self.registry[parent] then + self:Unregister(parent) + end + local info = new(...) + if type(info.children) == "table" then + local err, position = validateOptions(info.children) + + if err then + if position then + Dewdrop:error(position .. ": " .. err) + else + Dewdrop:error(err) + end + end + end + self.registry[parent] = info + if not info.dontHook and not self.onceRegistered[parent] then + if parent:HasScript("OnMouseUp") then + local script = parent:GetScript("OnMouseUp") + parent:SetScript("OnMouseUp", function() + if script then + script() + end + if arg1 == "RightButton" and self.registry[parent] then + if self:IsOpen(parent) then + self:Close() + else + self:Open(parent) + end + end + end) + end + if parent:HasScript("OnMouseDown") then + local script = parent:GetScript("OnMouseDown") + parent:SetScript("OnMouseDown", function() + if script then + script() + end + if self.registry[parent] then + self:Close() + end + end) + end + end + self.onceRegistered[parent] = true +end + +function Dewdrop:Unregister(parent) + self:argCheck(parent, 2, "table") + self.registry[parent] = nil +end + +function Dewdrop:Open(parent, ...) + self:argCheck(parent, 2, "table") + local info + local k1 = ... + if type(k1) == "table" and k1[0] and k1.IsFrameType and self.registry[k1] then + info = tmp() + for k,v in pairs(self.registry[k1]) do + info[k] = v + end + else + info = tmp(...) + if self.registry[parent] then + for k,v in pairs(self.registry[parent]) do + if info[k] == nil then + info[k] = v + end + end + end + end + local point = info.point + local relativePoint = info.relativePoint + local cursorX = info.cursorX + local cursorY = info.cursorY + if type(point) == "function" then + local b + point, b = point(parent) + if b then + relativePoint = b + end + end + if type(relativePoint) == "function" then + relativePoint = relativePoint(parent) + end + Open(self, parent, info.children, 1, nil, point, relativePoint, cursorX, cursorY) +end + +function Clear(self, level) + if level then + if level.buttons then + for i = #level.buttons, 1, -1 do + ReleaseButton(self, level, i) + end + end + end +end + +function Dewdrop:Close(level) + if DropDownList1:IsShown() then + DropDownList1:Hide() + end + if DewdropLib then + local d = DewdropLib:GetInstance('1.0') + local ret, val = pcall(d, IsOpen, d) + if ret and val then + DewdropLib:GetInstance('1.0'):Close() + end + end + self:argCheck(level, 2, "number", "nil") + if not level then + level = 1 + end + if level == 1 and levels[level] then + levels[level].parented = false + end + if level > 1 and levels[level-1].buttons then + local buttons = levels[level-1].buttons + for _,button in ipairs(buttons) do +-- button.arrow:SetWidth(16) +-- button.arrow:SetHeight(16) + button.selected = nil + button.highlight:Hide() +-- button.arrow:SetVertexColor(1, 1, 1) + end + end + if sliderFrame and sliderFrame.level >= level then + sliderFrame:Hide() + end + if editBoxFrame and editBoxFrame.level >= level then + editBoxFrame:Hide() + end + for i = level, #levels do + Clear(self, levels[level]) + levels[i]:Hide() + levels[i]:ClearAllPoints() + levels[i]:SetPoint("CENTER", UIParent, "CENTER") + levels[i].value = nil + end +end + +function Dewdrop:AddLine(...) + local info = tmp(...) + local level = info.level or currentLevel + info.level = nil + local button = AcquireButton(self, level) + if not next(info) then + info.disabled = true + end + button.disabled = info.isTitle or info.notClickable or info.disabled + button.isTitle = info.isTitle + button.notClickable = info.notClickable + if button.isTitle then + button.text:SetFontObject(GameFontNormalSmall) + elseif button.notClickable then + button.text:SetFontObject(GameFontHighlightSmall) + elseif button.disabled then + button.text:SetFontObject(GameFontDisableSmall) + else + button.text:SetFontObject(GameFontHighlightSmall) + end + if info.disabled then + button.arrow:SetDesaturated(true) + button.check:SetDesaturated(true) + else + button.arrow:SetDesaturated(false) + button.check:SetDesaturated(false) + end + if info.textR and info.textG and info.textB then + button.textR = info.textR + button.textG = info.textG + button.textB = info.textB + button.text:SetTextColor(button.textR, button.textG, button.textB) + else + button.text:SetTextColor(button.text:GetFontObject():GetTextColor()) + end + button.notCheckable = info.notCheckable + button.text:SetPoint("LEFT", button, "LEFT", button.notCheckable and 0 or 24, 0) + button.checked = not info.notCheckable and info.checked + button.isRadio = not info.notCheckable and info.isRadio + if info.isRadio then + button.check:Show() + button.check:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") + if button.checked then + button.check:SetTexCoord(0.25, 0.5, 0, 1) + button.check:SetVertexColor(1, 1, 1, 1) + else + button.check:SetTexCoord(0, 0.25, 0, 1) + button.check:SetVertexColor(1, 1, 1, 0.5) + end + button.radioHighlight:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") + button.check:SetWidth(16) + button.check:SetHeight(16) + elseif info.icon then + button.check:Show() + button.check:SetTexture(info.icon) + if info.iconWidth and info.iconHeight then + button.check:SetWidth(info.iconWidth) + button.check:SetHeight(info.iconHeight) + else + button.check:SetWidth(16) + button.check:SetHeight(16) + end + if info.iconCoordLeft and info.iconCoordRight and info.iconCoordTop and info.iconCoordBottom then + button.check:SetTexCoord(info.iconCoordLeft, info.iconCoordRight, info.iconCoordTop, info.iconCoordBottom) + elseif info.icon:find("^Interface\\Icons\\") then + button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + button.check:SetTexCoord(0, 1, 0, 1) + end + button.check:SetVertexColor(1, 1, 1, 1) + else + if button.checked then + if info.checkIcon then + button.check:SetWidth(16) + button.check:SetHeight(16) + button.check:SetTexture(info.checkIcon) + if info.checkIcon:find("^Interface\\Icons\\") then + button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + button.check:SetTexCoord(0, 1, 0, 1) + end + else + button.check:SetWidth(24) + button.check:SetHeight(24) + button.check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + button.check:SetTexCoord(0, 1, 0, 1) + end + button.check:SetVertexColor(1, 1, 1, 1) + else + button.check:SetVertexColor(1, 1, 1, 0) + end + end + if not button.disabled then + button.func = info.func + end + button.hasColorSwatch = info.hasColorSwatch + if button.hasColorSwatch then + button.colorSwatch:Show() + button.colorSwatch.texture:Show() + button.r = info.r or 1 + button.g = info.g or 1 + button.b = info.b or 1 + button.colorSwatch.texture:SetTexture(button.r, button.g, button.b) + button.checked = false + button.func = nil + button.colorFunc = info.colorFunc + local i = 1 + while true do + local k = "colorArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.hasOpacity = info.hasOpacity + button.opacity = info.opacity or 1 + else + button.colorSwatch:Hide() + button.colorSwatch.texture:Hide() + end + button.hasArrow = not button.hasColorSwatch and (info.value or info.hasSlider or info.hasEditBox) and info.hasArrow + if button.hasArrow then + button.arrow:SetAlpha(1) + if info.hasSlider then + button.hasSlider = true + button.sliderMin = info.sliderMin or 0 + button.sliderMax = info.sliderMax or 1 + button.sliderStep = info.sliderStep or 0 + button.sliderIsPercent = info.sliderIsPercent and true or false + button.sliderMinText = info.sliderMinText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMin * 100) or button.sliderMin + button.sliderMaxText = info.sliderMaxText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMax * 100) or button.sliderMax + button.sliderFunc = info.sliderFunc + button.sliderValue = info.sliderValue + local i = 1 + while true do + local k = "sliderArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + elseif info.hasEditBox then + button.hasEditBox = true + button.editBoxText = info.editBoxText or "" + button.editBoxFunc = info.editBoxFunc + local i = 1 + while true do + local k = "editBoxArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.editBoxChangeFunc = info.editBoxChangeFunc + local i = 1 + while true do + local k = "editBoxChangeArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.editBoxValidateFunc = info.editBoxValidateFunc + local i = 1 + while true do + local k = "editBoxValidateArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.editBoxIsKeybinding = info.editBoxIsKeybinding + button.editBoxKeybindingOnly = info.editBoxKeybindingOnly + button.editBoxKeybindingExcept = info.editBoxKeybindingExcept + else + button.value = info.value + local l = levels[level+1] + if l and info.value == l.value then +-- button.arrow:SetWidth(24) +-- button.arrow:SetHeight(24) + button.selected = true + button.highlight:Show() + end + end + else + button.arrow:SetAlpha(0) + end + local i = 1 + while true do + local k = "arg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.closeWhenClicked = info.closeWhenClicked + button.textHeight = info.textHeight or UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT or 10 + local font,_ = button.text:GetFont() + button.text:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF", button.textHeight) + button:SetHeight(button.textHeight + 6) + button.text:SetPoint("RIGHT", button.arrow, (button.hasColorSwatch or button.hasArrow) and "LEFT" or "RIGHT") + button.text:SetJustifyH(info.justifyH or "LEFT") + button.text:SetText(info.text) + button.tooltipTitle = info.tooltipTitle + button.tooltipText = info.tooltipText + button.tooltipFunc = info.tooltipFunc + local i = 1 + while true do + local k = "tooltipArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + if not button.tooltipTitle and not button.tooltipText and not button.tooltipFunc and not info.isTitle then + button.tooltipTitle = info.text + end + if type(button.func) == "string" then + self:assert(type(button.arg1) == "table", "Cannot call method " .. button.func .. " on a non-table") + self:assert(type(button.arg1[button.func]) == "function", "Method " .. button.func .. " nonexistant.") + end +end + +function Dewdrop:InjectAceOptionsTable(handler, options) + self:argCheck(handler, 2, "table") + self:argCheck(options, 3, "table") + if tostring(options.type):lower() ~= "group" then + self:error('Cannot inject into options table argument #3 if its type is not "group"') + end + if options.handler ~= nil and options.handler ~= handler then + self:error("Cannot inject into options table argument #3 if it has a different handler than argument #2") + end + options.handler = handler + local class = handler.class + if not AceLibrary:HasInstance("AceOO-2.0") or not class then + self:error("Cannot retrieve AceOptions tables from a non-object argument #2") + end + while class and class ~= AceLibrary("AceOO-2.0").Class do + if type(class.GetAceOptionsDataTable) == "function" then + local t = class:GetAceOptionsDataTable(handler) + for k,v in pairs(t) do + if type(options.args) ~= "table" then + options.args = {} + end + if options.args[k] == nil then + options.args[k] = v + end + end + end + local mixins = class.mixins + if mixins then + for mixin in pairs(mixins) do + if type(mixin.GetAceOptionsDataTable) == "function" then + local t = mixin:GetAceOptionsDataTable(handler) + for k,v in pairs(t) do + if type(options.args) ~= "table" then + options.args = {} + end + if options.args[k] == nil then + options.args[k] = v + end + end + end + end + end + class = class.super + end + return options +end + +local function activate(self, oldLib, oldDeactivate) + Dewdrop = self + if oldLib and oldLib.registry then + self.registry = oldLib.registry + self.onceRegistered = oldLib.onceRegistered + else + self.registry = {} + self.onceRegistered = {} + + local WorldFrame_OnMouseDown = WorldFrame:GetScript("OnMouseDown") + local WorldFrame_OnMouseUp = WorldFrame:GetScript("OnMouseUp") + local oldX, oldY, clickTime + WorldFrame:SetScript("OnMouseDown", function() + oldX,oldY = GetCursorPosition() + clickTime = GetTime() + if WorldFrame_OnMouseDown then + WorldFrame_OnMouseDown() + end + end) + + WorldFrame:SetScript("OnMouseUp", function() + local x,y = GetCursorPosition() + if not oldX or not oldY or not x or not y or not clickTime then + self:Close() + if WorldFrame_OnMouseUp then + WorldFrame_OnMouseUp() + end + return + end + local d = math.abs(x - oldX) + math.abs(y - oldY) + if d <= 5 and GetTime() - clickTime < 0.5 then + self:Close() + end + if WorldFrame_OnMouseUp then + WorldFrame_OnMouseUp() + end + end) + + hooksecurefunc(DropDownList1, "Show", function() + if levels[1] and levels[1]:IsVisible() then + self:Close() + end + end) + + hooksecurefunc("HideDropDownMenu", function() + if levels[1] and levels[1]:IsVisible() then + self:Close() + end + end) + + hooksecurefunc("CloseDropDownMenus", function() + if levels[1] and levels[1]:IsVisible() then + local stack = debugstack() + if not stack:find("`TargetFrame_OnHide'") then + self:Close() + end + end + end) + end + self.frame = oldLib and oldLib.frame or CreateFrame("Frame") + self.frame:UnregisterAllEvents() + self.frame:RegisterEvent("PLAYER_REGEN_ENABLED") + self.frame:RegisterEvent("PLAYER_REGEN_DISABLED") + self.frame:Hide() + self.frame:SetScript("OnEvent", function(this, event) + this:Show() + end) + self.frame:SetScript("OnUpdate", function(this) + this:Hide() + self:Refresh(1) + end) + levels = {} + buttons = {} + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(Dewdrop, MAJOR_VERSION, MINOR_VERSION, activate) diff --git a/libs/FuBarPlugin-2.0/FuBarPlugin-2.0.lua b/libs/FuBarPlugin-2.0/FuBarPlugin-2.0.lua new file mode 100644 index 0000000..65d0fa0 --- /dev/null +++ b/libs/FuBarPlugin-2.0/FuBarPlugin-2.0.lua @@ -0,0 +1,1585 @@ +--[[ +Name: FuBarPlugin-2.0 +Revision: $Rev: 23187 $ +Author: Cameron Kenneth Knight (ckknight@gmail.com) +Website: http://wiki.wowace.com/index.php/FuBarPlugin-2.0 +Documentation: http://wiki.wowace.com/index.php/FuBarPlugin-2.0 +SVN: svn://svn.wowace.com/root/branches/FuBar/FuBarPlugin-2.0/FuBarPlugin-2.0/ +Description: Plugin for FuBar. +Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, Tablet-2.0, Dewdrop-2.0 +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "FuBarPlugin-2.0" +local MINIMAPCONTAINER_MAJOR_VERSION = "FuBarPlugin-MinimapContainer-2.0" +local MINOR_VERSION = "$Revision: 23187 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end + +local AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0") +local Tablet = AceLibrary:HasInstance("Tablet-2.0") and AceLibrary("Tablet-2.0") +local Dewdrop = AceLibrary:HasInstance("Dewdrop-2.0") and AceLibrary("Dewdrop-2.0") + +local epsilon = 1e-5 +local _G = getfenv(0) + +local SHOW_ICON = "Show icon" +local SHOW_ICON_DESC = "Show the plugins icon on the panel." +local SHOW_TEXT = "Show text" +local SHOW_TEXT_DESC = "Show the plugins text on the panel." +local SHOW_COLORED_TEXT = "Show colored text" +local SHOW_COLORED_TEXT_DESC = "Allow the plugin to color its text." +local DETACH_TOOLTIP = "Detach tooltip" +local DETACH_TOOLTIP_DESC = "Detach the tooltip from the panel." +local LOCK_TOOLTIP = "Lock tooltip" +local LOCK_TOOLTIP_DESC = "Lock the tooltips position. When the tooltip is locked, you must use Alt to access it with your mouse." +local POSITION = "Position" +local POSITION_DESC = "Position the plugin on the panel." +local POSITION_LEFT = "Left" +local POSITION_RIGHT = "Right" +local POSITION_CENTER = "Center" +local ATTACH_TO_MINIMAP = "Attach to minimap" +local ATTACH_TO_MINIMAP_DESC = "Attach the plugin to the minimap instead of the panel." +local HIDE_FUBAR_PLUGIN = "Hide plugin" +local HIDE_FUBAR_PLUGIN_CMD = "Hidden" +local HIDE_FUBAR_PLUGIN_DESC = "Hide the plugin from the panel or minimap, leaving the addon running." +local OTHER = "Other" +local CLOSE = "Close" +local CLOSE_DESC = "Close the menu." + +if GetLocale() == "koKR" then + SHOW_ICON = "아이콘 표시" + SHOW_ICON_DESC = "패널에 플러그인 아이콘을 표시합니다." + SHOW_TEXT = "텍스트 표시" + SHOW_TEXT_DESC = "페널에 플러그인 텍스트를 표시합니다." + SHOW_COLORED_TEXT = "색상화된 텍스트 표시" + SHOW_COLORED_TEXT_DESC = "플러그인의 텍스트 색상을 허용합니다." + DETACH_TOOLTIP = "툴팁 분리" + DETACH_TOOLTIP_DESC = "패널에서 툴팁을 분리 합니다." + LOCK_TOOLTIP = "툴팁 고정" + LOCK_TOOLTIP_DESC = "툴팁 위치를 고정합니다." + POSITION = "위치" + POSITION_DESC = "패널에서 플러그인의 위치를 설정합니다." + POSITION_LEFT = "왼쪽" + POSITION_RIGHT = "오른쪽" + POSITION_CENTER = "가운데" + ATTACH_TO_MINIMAP = "미니맵에 표시" + ATTACH_TO_MINIMAP_DESC = "플러그인을 패널 대신 미니맵에 표시합니다." + HIDE_FUBAR_PLUGIN = "FuBar 플러그인 숨기기" + HIDE_FUBAR_PLUGIN_CMD = "숨겨짐" + HIDE_FUBAR_PLUGIN_DESC = "패널에서 플러그인을 숨깁니다." + OTHER = "기타" + CLOSE = "닫기" + CLOSE_DESC = "메뉴 닫기." +elseif GetLocale() == "deDE" then + SHOW_ICON = "Zeige Icon" + SHOW_ICON_DESC = "Zeige das Plugin-Icon auf der Leiste." + SHOW_TEXT = "Zeige Text" + SHOW_TEXT_DESC = "Zeige den Plugin-Text auf der Leiste." + SHOW_COLORED_TEXT = "Zeige gef\195\164rbten Text" + SHOW_COLORED_TEXT_DESC = "Dem Plugin erlauben sein Text zu f\195\164rben." + DETACH_TOOLTIP = "Tooltip l\195\182sen" + DETACH_TOOLTIP_DESC = "Tooltip von der Leiste l\195\182sen." + LOCK_TOOLTIP = "Tooltip sperren" + LOCK_TOOLTIP_DESC = "Tooltip an der Position sperren." + POSITION = "Position" + POSITION_DESC = "Positioniert das Plugin auf der Leiste." + POSITION_LEFT = "Links" + POSITION_RIGHT = "Rechts" + POSITION_CENTER = "Mitte" + ATTACH_TO_MINIMAP = "An der Minimap anbringen" + ATTACH_TO_MINIMAP_DESC = "Bringt das Plugin an der Minimap anstelle der Leiste an." + HIDE_FUBAR_PLUGIN = "Versteckt das FuBar Plugin" + HIDE_FUBAR_PLUGIN_CMD = "Verstecken" + HIDE_FUBAR_PLUGIN_DESC = "Versteckt das Plugin von der Leiste." +elseif GetLocale() == "frFR" then + SHOW_ICON = "Afficher l'ic\195\180ne" + SHOW_ICON_DESC = "Afficher l'ic\195\180ne du plugin sur le panneau." + SHOW_TEXT = "Afficher le texte" + SHOW_TEXT_DESC = "Afficher le texte du plugin sur le panneau." + SHOW_COLORED_TEXT = "Afficher la couleur du texte" + SHOW_COLORED_TEXT_DESC = "Permet au plugin de colorer le texte." + DETACH_TOOLTIP = "D\195\169tacher le tooltip" + DETACH_TOOLTIP_DESC = "Permet de d\195\169tacher le tooltip du panneau." + LOCK_TOOLTIP = "Bloquer le tooltip" + LOCK_TOOLTIP_DESC = "Permet de bloquer le tooltip \195\160 sa position actuelle. Une fois le tooltip bloqu\195\169, vous devez utiliser la touche Alt pour le d\195\169placer avec votre souris." + POSITION = "Position" + POSITION_DESC = "Permet de changer la position du plugin dans le panneau." + POSITION_LEFT = "Gauche" + POSITION_RIGHT = "Droite" + POSITION_CENTER = "Centre" + ATTACH_TO_MINIMAP = "Attacher \195\160 la minicarte" + ATTACH_TO_MINIMAP_DESC = "Attache l'ic\195\180ne du plugin \195\160 la minicarte." + HIDE_FUBAR_PLUGIN = "Masquer le plugin" + HIDE_FUBAR_PLUGIN_CMD = "Masqu\195\169" + HIDE_FUBAR_PLUGIN_DESC = "Permet de masquer compl\195\168tement le plugin du panneau, mais laisse l'addon fonctionner." + OTHER = "Autre" + CLOSE = "Fermer" + CLOSE_DESC = "Ferme le menu." +elseif GetLocale() == "zhCN" then + SHOW_ICON = "显示图标" + SHOW_ICON_DESC = "在面板上显示插件图标." + SHOW_TEXT = "显示文字" + SHOW_TEXT_DESC = "在面板上显示文字标题." + SHOW_COLORED_TEXT = "显示彩色文字" + SHOW_COLORED_TEXT_DESC = "允许插件显示彩色文字." + DETACH_TOOLTIP = "独立提示信息" + DETACH_TOOLTIP_DESC = "从面板上独立提示信息." + LOCK_TOOLTIP = "锁定提示信息" + LOCK_TOOLTIP_DESC = "锁定提示信息位置." + POSITION = "位置" + POSITION_DESC = "插件在面板上的位置." + POSITION_LEFT = "居左" + POSITION_RIGHT = "居右" + POSITION_CENTER = "居中" + ATTACH_TO_MINIMAP = "依附在小地图" + ATTACH_TO_MINIMAP_DESC = "插件图标依附在小地图而不显示在面板上." + HIDE_FUBAR_PLUGIN = "隐藏FuBar插件" + HIDE_FUBAR_PLUGIN_CMD = "Hidden" + HIDE_FUBAR_PLUGIN_DESC = "在面板上隐藏该插件." +elseif GetLocale() == "zhTW" then + SHOW_ICON = "顯示圖示" + SHOW_ICON_DESC = "在面板上顯示插件圖示。" + SHOW_TEXT = "顯示文字" + SHOW_TEXT_DESC = "在面板上顯示文字標題。" + SHOW_COLORED_TEXT = "顯示彩色文字" + SHOW_COLORED_TEXT_DESC = "允許插件顯示彩色文字。" + DETACH_TOOLTIP = "獨立提示訊息" + DETACH_TOOLTIP_DESC = "從面板上獨立提示訊息。" + LOCK_TOOLTIP = "鎖定提示訊息" + LOCK_TOOLTIP_DESC = "鎖定提示訊息位置。" + POSITION = "位置" + POSITION_DESC = "插件在面板上的位置。" + POSITION_LEFT = "靠左" + POSITION_RIGHT = "靠右" + POSITION_CENTER = "置中" + ATTACH_TO_MINIMAP = "依附在小地圖" + ATTACH_TO_MINIMAP_DESC = "插件圖標依附在小地圖而不顯示在面板上。" + HIDE_FUBAR_PLUGIN = "隱藏FuBar插件" + HIDE_FUBAR_PLUGIN_CMD = "Hidden" + HIDE_FUBAR_PLUGIN_DESC = "在面板上隱藏該插件." +elseif GetLocale() == "esES" then + SHOW_ICON = "Mostrar icono" + SHOW_ICON_DESC = "Muestra el icono del plugin en el panel" + SHOW_TEXT = "Mostrar texto" + SHOW_TEXT_DESC = "Muestra el texto del plugin en el panel" + SHOW_COLORED_TEXT = "Mostrar el texto en color" + SHOW_COLORED_TEXT_DESC = "Permite al plugin colorear su texto" + DETACH_TOOLTIP = "Separar tooltip" + DETACH_TOOLTIP_DESC = "Separa el tooltip del panel" + LOCK_TOOLTIP = "Bloquear tooltip" + LOCK_TOOLTIP_DESC = "Bloquea la posici\195\179n de los tooltips. Cuando el tooltip est\195\161 bloqueado debes usar Alt para acceder a \195\169l con el rat\195\179n" + POSITION = "Posici\195\179n" + POSITION_DESC = "Posici\195\179n del plugin en el panel" + POSITION_LEFT = "Izquierda" + POSITION_RIGHT = "Derecha" + POSITION_CENTER = "Centro" + ATTACH_TO_MINIMAP = "Adjuntar al minimapa" + ATTACH_TO_MINIMAP_DESC = "Adjunta el plugin al minimapa en vez de al panel." + HIDE_FUBAR_PLUGIN = "Ocultar plugin" + HIDE_FUBAR_PLUGIN_CMD = "Oculto" + HIDE_FUBAR_PLUGIN_DESC = "Oculta el plugin del panel o minimapa, dejando el accesorio funcionando." + OTHER = "Otros" + CLOSE = "Cerrar" + CLOSE_DESC = "Cierra el men\195\186." +end + +local AceOO = AceLibrary("AceOO-2.0") +local FuBarPlugin = AceOO.Mixin { + "GetTitle", + "GetName", + "GetCategory", + "SetFontSize", + "GetFrame", + "Show", + "Hide", + "GetPanel", + "IsTextColored", + "ToggleTextColored", + "IsMinimapAttached", + "ToggleMinimapAttached", + "Update", + "UpdateDisplay", + "UpdateData", + "UpdateText", + "UpdateTooltip", + "SetIcon", + "GetIcon", + "CheckWidth", + "SetText", + "GetText", + "IsIconShown", + "ToggleIconShown", + "ShowIcon", + "HideIcon", + "IsTextShown", + "ToggleTextShown", + "ShowText", + "HideText", + "IsTooltipDetached", + "ToggleTooltipDetached", + "DetachTooltip", + "ReattachTooltip", + "GetDefaultPosition", + "SetPanel", + "IsLoadOnDemand", + "IsDisabled", + "CreateBasicPluginFrame", + "CreatePluginChildFrame", + "OpenMenu", + "AddImpliedMenuOptions", + } + +local good = nil +local function CheckFuBar() + if not good then + good = FuBar and tonumber(string.sub(FuBar.version, 1, 3)) and tonumber(string.sub(FuBar.version, 1, 3)) >= 2 and true + end + return good +end + +function FuBarPlugin:GetTitle() + local name = self.title or self.name + FuBarPlugin:assert(name, "You must provide self.title or self.name") + local _,_,title = string.find(name, "FuBar %- (.-)%s*$") + if not title then + title = name + end + return (string.gsub(string.gsub(title, "|c%x%x%x%x%x%x%x%x", ""), "|r", "")) +end + +function FuBarPlugin:GetName() + return self.name +end + +function FuBarPlugin:GetCategory() + return self.category or OTHER +end + +function FuBarPlugin:GetFrame() + return self.frame +end + +function FuBarPlugin:GetPanel() + return self.panel +end + +function FuBarPlugin:IsTextColored() + return not self.db or not self.db.profile or not self.db.profile.uncolored +end + +function FuBarPlugin:ToggleTextColored() + FuBarPlugin:assert(self.db, "Cannot change text color if self.db is not available. (" .. self:GetTitle() .. ")") + self.db.profile.uncolored = not self.db.profile.uncolored or nil + self:UpdateText() +end + +function FuBarPlugin:ToggleMinimapAttached() + if CheckFuBar() and not self.cannotAttachToMinimap then + local value = self:IsMinimapAttached() + if value then + if self.panel then + self.panel:RemovePlugin(self) + end + FuBar:GetPanel(1):AddPlugin(self, nil, self.defaultPosition) + else + if self.panel then + self.panel:RemovePlugin(self) + end + AceLibrary(MINIMAPCONTAINER_MAJOR_VERSION):AddPlugin(self) + end + end + Dewdrop:Close() +end + +function FuBarPlugin:IsMinimapAttached() + if not CheckFuBar() then + return true + end + return self.panel == AceLibrary(MINIMAPCONTAINER_MAJOR_VERSION) +end + +function FuBarPlugin:Update() + self:UpdateData() + self:UpdateText() + self:UpdateTooltip() +end + +function FuBarPlugin:UpdateDisplay() + self:UpdateText() + self:UpdateTooltip() +end + +function FuBarPlugin:UpdateData() + if type(self.OnDataUpdate) == "function" then + if not self:IsDisabled() then + self:OnDataUpdate() + end + end +end + +function FuBarPlugin:UpdateText() + if type(self.OnTextUpdate) == "function" then + if not self:IsDisabled() then + self:OnTextUpdate() + end + elseif self:IsTextShown() then + self:SetText(self:GetTitle()) + end +end + +function FuBarPlugin:RegisterTablet() + if not Tablet:IsRegistered(self.frame) then + if self.db and self.db.profile and not self.db.profile.detachedTooltip then + self.db.profile.detachedTooltip = {} + end + Tablet:Register(self.frame, + 'children', function() + Tablet:SetTitle(self:GetTitle()) + if type(self.OnTooltipUpdate) == "function" then + if not self:IsDisabled() then + self:OnTooltipUpdate() + end + end + end, + 'clickable', self.clickableTooltip, + 'data', CheckFuBar() and FuBar.db.profile.tooltip or self.db and self.db.profile.detachedTooltip or {}, + 'detachedData', self.db and self.db.profile.detachedTooltip or {}, + 'point', function(frame) + if frame:GetTop() > GetScreenHeight() / 2 then + local x = frame:GetCenter() + if x < GetScreenWidth() / 3 then + return "TOPLEFT", "BOTTOMLEFT" + elseif x < GetScreenWidth() * 2 / 3 then + return "TOP", "BOTTOM" + else + return "TOPRIGHT", "BOTTOMRIGHT" + end + else + local x = frame:GetCenter() + if x < GetScreenWidth() / 3 then + return "BOTTOMLEFT", "TOPLEFT" + elseif x < GetScreenWidth() * 2 / 3 then + return "BOTTOM", "TOP" + else + return "BOTTOMRIGHT", "TOPRIGHT" + end + end + end, + 'menu', self.OnMenuRequest and function(level, value, valueN_1, valueN_2, valueN_3, valueN_4) + if level == 1 then + local name = tostring(self) + if not string.find(name, '^table:') then + name = string.gsub(name, "|c%x%x%x%x%x%x%x%x(.-)|r", "%1") + Dewdrop:AddLine( + 'text', name, + 'isTitle', true + ) + end + end + if type(self.OnMenuRequest) == "function" then + self:OnMenuRequest(level, value, true, valueN_1, valueN_2, valueN_3, valueN_4) + elseif type(self.OnMenuRequest) == "table" then + Dewdrop:FeedAceOptionsTable(self.OnMenuRequest) + end + end, + 'hideWhenEmpty', self.tooltipHiddenWhenEmpty + ) + local func = self.frame:GetScript("OnEnter") + local function newFunc(...) + func(...) + + if FuBar and FuBar.IsHidingTooltipsInCombat and FuBar:IsHidingTooltipsInCombat() and InCombatLockdown() then + local frame = this.self.frame + if Tablet:IsAttached(frame) then + Tablet:Close(frame) + end + end + end + self.frame:SetScript("OnEnter", newFunc) + end +end + +function FuBarPlugin:UpdateTooltip() + FuBarPlugin.RegisterTablet(self) + if self:IsMinimapAttached() and not self:IsTooltipDetached() and self.minimapFrame then + Tablet:Refresh(self.minimapFrame) + else + Tablet:Refresh(self.frame) + end +end + +function FuBarPlugin:OnProfileEnable() + self:Update() +end + +function FuBarPlugin:Show(panelId) + if self.frame:IsShown() or (self.minimapFrame and self.minimapFrame:IsShown()) then + return + end + if panelId ~= false then + if self.db then + self.db.profile.hidden = nil + end + end + if self.IsActive and not self:IsActive() then + self.panelIdTmp = panelId + self:ToggleActive() + self.panelIdTmp = nil + if self.db then + self.db.profile.disabled = nil + end + elseif not self.db or not self.db.profile.hidden then + if panelId == 0 or not CheckFuBar() then + AceLibrary(MINIMAPCONTAINER_MAJOR_VERSION):AddPlugin(self) + else + FuBar:ShowPlugin(self, panelId or self.panelIdTmp) + end + if not self.userDefinedFrame then + if not self:IsTextShown() then + self.textFrame:SetText("") + self.textFrame:SetWidth(epsilon) + self.textFrame:Hide() + end + if not self:IsIconShown() then + self.iconFrame:SetWidth(epsilon) + self.iconFrame:Hide() + end + end + if AceOO.inherits(self, "AceAddon-2.0") then + local AceAddon = AceLibrary("AceAddon-2.0") + if AceAddon.addonsEnabled and not AceAddon.addonsEnabled[self] then + return + end + end + self:Update() + end +end + +function FuBarPlugin:Hide(check) + if not self.frame:IsShown() and (not self.minimapFrame or not self.minimapFrame:IsShown()) then + return + end + if self.hideWithoutStandby and self.db and check ~= false then + self.db.profile.hidden = true + end + if not self.hideWithoutStandby then + if self.db and not self.overrideTooltip and not self.cannotDetachTooltip and self:IsTooltipDetached() and self.db.profile.detachedTooltip and self.db.profile.detachedTooltip.detached then + self:ReattachTooltip() + self.db.profile.detachedTooltip.detached = true + end + if self.IsActive and self:IsActive() and self.ToggleActive and (not CheckFuBar() or not FuBar:IsChangingProfile()) then + self:ToggleActive() + end + end + if self.panel then + self.panel:RemovePlugin(self) + end + self.frame:Hide() + if self.minimapFrame then + self.minimapFrame:Hide() + end + + if Dewdrop:IsOpen(self.frame) or (self.minimapFrame and Dewdrop:IsOpen(self.minimapFrame)) then + Dewdrop:Close() + end +end + +function FuBarPlugin:SetIcon(path) + if not path then + return + end + FuBarPlugin:argCheck(path, 2, "string", "boolean") + FuBarPlugin:assert(self.hasIcon, "Cannot set icon unless self.hasIcon is set. (" .. self:GetTitle() .. ")") + if not self.iconFrame then + return + end + if type(path) ~= "string" then + path = format("Interface\\AddOns\\%s\\icon", FuBarPlugin.folderNames[self] or self.folderName) + elseif not string.find(path, '^Interface[\\/]') then + path = format("Interface\\AddOns\\%s\\%s", FuBarPlugin.folderNames[self] or self.folderName, path) + end + if string.sub(path, 1, 16) == "Interface\\Icons\\" then + self.iconFrame:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + self.iconFrame:SetTexCoord(0, 1, 0, 1) + end + self.iconFrame:SetTexture(path) + if self.minimapIcon then + if string.sub(path, 1, 16) == "Interface\\Icons\\" then + self.minimapIcon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + self.minimapIcon:SetTexCoord(0, 1, 0, 1) + end + self.minimapIcon:SetTexture(path) + end +end + +function FuBarPlugin:GetIcon() + if self.hasIcon then + return self.iconFrame:GetTexture() + end +end + +function FuBarPlugin:CheckWidth(force) + FuBarPlugin:argCheck(force, 2, "boolean", "nil") + if (self.iconFrame and self.iconFrame:IsShown()) or (self.textFrame and self.textFrame:IsShown()) then + if (self.db and self.db.profile and not self:IsIconShown()) or not self.hasIcon then + self.iconFrame:SetWidth(epsilon) + end + local width + if not self.hasNoText then + self.textFrame:SetHeight(0) + self.textFrame:SetWidth(500) + width = self.textFrame:GetStringWidth() + 1 + self.textFrame:SetWidth(width) + self.textFrame:SetHeight(self.textFrame:GetHeight()) + end + if self.hasNoText or not self.textFrame:IsShown() then + self.frame:SetWidth(self.iconFrame:GetWidth()) + if self.panel and self.panel:GetPluginSide(self) == "CENTER" then + self.panel:UpdateCenteredPosition() + end + elseif force or not self.textWidth or self.textWidth < width or self.textWidth - 8 > width then + self.textWidth = width + self.textFrame:SetWidth(width) + if self.iconFrame and self.iconFrame:IsShown() then + self.frame:SetWidth(width + self.iconFrame:GetWidth()) + else + self.frame:SetWidth(width) + end + if self.panel and self.panel:GetPluginSide(self) == "CENTER" then + self.panel:UpdateCenteredPosition() + end + end + end +end + +function FuBarPlugin:SetText(text) + if not self.textFrame then + return + end + FuBarPlugin:assert(not self.hasNoText, "Cannot set text if self.hasNoText has been set. (" .. self:GetTitle() .. ")") + FuBarPlugin:argCheck(text, 2, "string", "number") + if text == "" then + if self.hasIcon then + self:ShowIcon() + else + text = self:GetTitle() + end + end + if not self:IsTextColored() then + text = string.gsub(string.gsub(text, "|c%x%x%x%x%x%x%x%x", ""), "|r", "") + end + self.textFrame:SetText(text) + self:CheckWidth() +end + +function FuBarPlugin:GetText() + FuBarPlugin:assert(self.textFrame, "Cannot get text without a self.textFrame (" .. self:GetTitle() .. ")") + if not self.hasNoText then + return self.textFrame:GetText() or "" + end +end + +function FuBarPlugin:IsIconShown() + if not self.hasIcon then + return false + elseif self.hasNoText then + return true + elseif not self.db then + return true + elseif self.db and self.db.profile.showIcon == nil then + return true + else + return (self.db and (self.db.profile.showIcon == 1 or self.db.profile.showIcon == true)) and true or false + end +end + +function FuBarPlugin:ToggleIconShown() + FuBarPlugin:assert(self.iconFrame, "Cannot toggle icon without a self.iconFrame (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.hasIcon, "Cannot show icon unless self.hasIcon is set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(not self.hasNoText, "Cannot hide icon if self.hasNoText is set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.textFrame, "Cannot hide icon if self.textFrame is not set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.iconFrame, "Cannot hide icon if self.iconFrame is not set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.db, "Cannot hide icon if self.db is not available. (" .. self:GetTitle() .. ")") + local value = not self:IsIconShown() + self.db.profile.showIcon = value + if value then + if not self:IsTextShown() and self.textFrame:IsShown() and self.textFrame:GetText() == self:GetTitle() then + self.textFrame:Hide() + self.textFrame:SetText("") + end + self.iconFrame:Show() + self.iconFrame:SetWidth(self.iconFrame:GetHeight()) + else + if not self.textFrame:IsShown() or not self.textFrame:GetText() then + self.textFrame:Show() + self.textFrame:SetText(self:GetTitle()) + end + self.iconFrame:Hide() + self.iconFrame:SetWidth(epsilon) + end + self:CheckWidth(true) + return value +end + +function FuBarPlugin:ShowIcon() + if not self:IsIconShown() then + self:ToggleIconShown() + end +end + +function FuBarPlugin:HideIcon() + if self:IsIconShown() then + self:ToggleIconShown() + end +end + +function FuBarPlugin:IsTextShown() + if self.hasNoText then + return false + elseif not self.hasIcon then + return true + elseif not self.db then + return true + elseif self.db and self.db.profile.showText == nil then + return true + else + return (self.db and (self.db.profile.showText == 1 or self.db.profile.showText == true)) and true or false + end +end + +function FuBarPlugin:ToggleTextShown() + FuBarPlugin:assert(not self.cannotHideText, "Cannot hide text unless self.cannotHideText is unset. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.hasIcon, "Cannot show text unless self.hasIcon is set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(not self.hasNoText, "Cannot hide text if self.hasNoText is set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.textFrame, "Cannot hide text if self.textFrame is not set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.iconFrame, "Cannot hide text if self.iconFrame is not set. (" .. self:GetTitle() .. ")") + FuBarPlugin:assert(self.db, "Cannot hide text if self.db is not available. (" .. self:GetTitle() .. ")") + local value = not self:IsTextShown() + self.db.profile.showText = value + if value then + self.textFrame:Show() + self:UpdateText() + else + self.textFrame:SetText("") + self.textFrame:SetWidth(epsilon) + self.textFrame:Hide() + if not self:IsIconShown() then + DropDownList1:Hide() + end + self:ShowIcon() + end + self:CheckWidth(true) + return value +end + +function FuBarPlugin:ShowText() + if not self:IsTextShown() then + self:ToggleTextShown() + end +end + +function FuBarPlugin:HideText() + if self:IsTextShown() then + self:ToggleTextShown() + end +end + +function FuBarPlugin:IsTooltipDetached() + FuBarPlugin.RegisterTablet(self) + return not Tablet:IsAttached(self.frame) +end + +function FuBarPlugin:ToggleTooltipDetached() + FuBarPlugin.RegisterTablet(self) + if self:IsTooltipDetached() then + Tablet:Attach(self.frame) + else + Tablet:Detach(self.frame) + end + if Dewdrop then Dewdrop:Close() end +end + +function FuBarPlugin:DetachTooltip() + FuBarPlugin.RegisterTablet(self) + Tablet:Detach(self.frame) +end + +function FuBarPlugin:ReattachTooltip() + FuBarPlugin.RegisterTablet(self) + Tablet:Attach(self.frame) +end + +function FuBarPlugin:GetDefaultPosition() + return self.defaultPosition or "LEFT" +end + +local function IsCorrectPanel(panel) + if type(panel) ~= "table" then + return false + elseif type(panel.AddPlugin) ~= "function" then + return false + elseif type(panel.RemovePlugin) ~= "function" then + return false + elseif type(panel.GetNumPlugins) ~= "function" then + return false + elseif type(panel:GetNumPlugins()) ~= "number" then + return false + elseif type(panel.GetPlugin) ~= "function" then + return false + elseif type(panel.HasPlugin) ~= "function" then + return false + elseif type(panel.GetPluginSide) ~= "function" then + return false + end + return true +end + +function FuBarPlugin:SetPanel(panel) + if panel then + FuBarPlugin:assert(IsCorrectPanel(panel), "Bad argument #2 to `SetPanel'. Panel does not have the correct API.") + end + self.panel = panel +end + +function FuBarPlugin:SetFontSize(size) + FuBarPlugin:assert(not self.userDefinedFrame, (self.name and self.name .. ": " or "") .. "You must provide a SetFontSize(size) method if you provide your own frame.") + if self.hasIcon then + FuBarPlugin:assert(self.iconFrame, (self.name and self.name .. ": " or "") .. "No iconFrame found") + self.iconFrame:SetWidth(size + 3) + self.iconFrame:SetHeight(size + 3) + end + if not self.hasNoText then + FuBarPlugin:assert(self.textFrame, (self.name and self.name .. ": " or "") .. "No textFrame found") + local font, _, flags = self.textFrame:GetFont() + self.textFrame:SetFont(font, size, flags) + end + self:CheckWidth() +end + +function FuBarPlugin:IsLoadOnDemand() + return IsAddOnLoadOnDemand(FuBarPlugin.folderNames[self] or self.folderName) +end + +function FuBarPlugin:IsDisabled() + return self.IsActive and not self:IsActive() or false +end + +function FuBarPlugin:OnInstanceInit(target) + if not AceEvent then + self:error(MAJOR_VERSION .. " requires AceEvent-2.0.") + elseif not Tablet then + self:error(MAJOR_VERSION .. " requires Tablet-2.0.") + elseif not Dewdrop then + self:error(MAJOR_VERSION .. " requires Dewdrop-2.0.") + end + self.registry[target] = true + + local _,_,folderName = string.find(debugstack(6, 1, 0), "\\AddOns\\(.*)\\") + target.folderName = folderName + self.folderNames[target] = folderName +end + +local frame_OnClick, frame_OnDoubleClick, frame_OnMouseDown, frame_OnMouseUp, frame_OnReceiveDrag, frame_OnEnter, frame_OnLeave + +function FuBarPlugin:CreateBasicPluginFrame(name) + local frame = CreateFrame("Button", name, UIParent) + frame:SetFrameStrata("HIGH") + frame:SetFrameLevel(7) + frame:EnableMouse(true) + frame:EnableMouseWheel(true) + frame:SetMovable(true) + frame:SetWidth(150) + frame:SetHeight(24) + frame:SetPoint("CENTER", UIParent, "CENTER") + frame.self = self + if not frame_OnEnter then + function frame_OnEnter() + if type(this.self.OnEnter) == "function" then + this.self:OnEnter() + end + end + end + frame:SetScript("OnEnter", frame_OnEnter) + if not frame_OnLeave then + function frame_OnLeave() + if type(this.self.OnLeave) == "function" then + this.self:OnLeave() + end + end + end + frame:SetScript("OnLeave", frame_OnLeave) + if not frame_OnClick then + function frame_OnClick() + if type(this.self.OnClick) == "function" then + this.self:OnClick(arg1) + end + end + end + frame:SetScript("OnClick", frame_OnClick) + if not frame_OnDoubleClick then + function frame_OnDoubleClick() + if type(this.self.OnDoubleClick) == "function" then + this.self:OnDoubleClick(arg1) + end + end + end + frame:SetScript("OnDoubleClick", frame_OnDoubleClick) + if not frame_OnMouseDown then + function frame_OnMouseDown() + if arg1 == "RightButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then + this.self:OpenMenu() + return + else + HideDropDownMenu(1) + if type(this.self.OnMouseDown) == "function" then + this.self:OnMouseDown(arg1) + end + end + end + end + frame:SetScript("OnMouseDown", frame_OnMouseDown) + if not frame_OnMouseUp then + function frame_OnMouseUp() + if type(this.self.OnMouseUp) == "function" then + this.self:OnMouseUp(arg1) + end + end + end + frame:SetScript("OnMouseUp", frame_OnMouseUp) + if not frame_OnReceiveDrag then + function frame_OnReceiveDrag() + if type(this.self.OnReceiveDrag) == "function" then + this.self:OnReceiveDrag() + end + end + end + frame:SetScript("OnReceiveDrag", frame_OnReceiveDrag) + return frame +end + +local child_OnEnter, child_OnLeave, child_OnClick, child_OnDoubleClick, child_OnMouseDown, child_OnMouseUp, child_OnReceiveDrag +function FuBarPlugin:CreatePluginChildFrame(frameType, name, parent) + FuBarPlugin:assert(self.frame, (self.name and self.name .. ": " or "") .. "You must have self.frame declared in order to add child frames") + FuBarPlugin:argCheck(frameType, 1, "string") + local child = CreateFrame(frameType, name, parent) + if parent then + child:SetFrameLevel(parent:GetFrameLevel() + 2) + end + child.self = self + if not child_OnEnter then + function child_OnEnter(...) + if this.self.frame:GetScript("OnEnter") then + this.self.frame:GetScript("OnEnter")(...) + end + end + end + child:SetScript("OnEnter", child_OnEnter) + if not child_OnLeave then + function child_OnLeave(...) + if this.self.frame:GetScript("OnLeave") then + this.self.frame:GetScript("OnLeave")(...) + end + end + end + child:SetScript("OnLeave", child_OnLeave) + if child:HasScript("OnClick") then + if not child_OnClick then + function child_OnClick(...) + if this.self.frame:HasScript("OnClick") and this.self.frame:GetScript("OnClick") then + this.self.frame:GetScript("OnClick")(...) + end + end + end + child:SetScript("OnClick", child_OnClick) + end + if child:HasScript("OnDoubleClick") then + if not child_OnDoubleClick then + function child_OnDoubleClick(...) + if this.self.frame:HasScript("OnDoubleClick") and this.self.frame:GetScript("OnDoubleClick") then + this.self.frame:GetScript("OnDoubleClick")(...) + end + end + end + child:SetScript("OnDoubleClick", child_OnDoubleClick) + end + if not child_OnMouseDown then + function child_OnMouseDown(...) + if this.self.frame:HasScript("OnMouseDown") and this.self.frame:GetScript("OnMouseDown") then + this.self.frame:GetScript("OnMouseDown")(...) + end + end + end + child:SetScript("OnMouseDown", child_OnMouseDown) + if not child_OnMouseUp then + function child_OnMouseUp(...) + if this.self.frame:HasScript("OnMouseUp") and this.self.frame:GetScript("OnMouseUp") then + this.self.frame:GetScript("OnMouseUp")(...) + end + end + end + child:SetScript("OnMouseUp", child_OnMouseUp) + if not child_OnReceiveDrag then + function child_OnReceiveDrag(this) + if this.self.frame:HasScript("OnReceiveDrag") and this.self.frame:GetScript("OnReceiveDrag") then + this.self.frame:GetScript("OnReceiveDrag")() + end + end + end + child:SetScript("OnReceiveDrag", child_OnReceiveDrag) + return child +end + +function FuBarPlugin:OpenMenu(frame) + if not frame then + frame = self:GetFrame() + end + if not frame or not self:GetFrame() or Dewdrop:IsOpen(frame) then + Dewdrop:Close() + return + end + Tablet:Close() + + if not Dewdrop:IsRegistered(self:GetFrame()) then + if type(self.OnMenuRequest) == "table" and (not self.OnMenuRequest.handler or self.OnMenuRequest.handler == self) and self.OnMenuRequest.type == "group" then + Dewdrop:InjectAceOptionsTable(self, self.OnMenuRequest) + if self.OnMenuRequest.args and CheckFuBar() and not self.independentProfile then + self.OnMenuRequest.args.profile = nil + end + end + Dewdrop:Register(self:GetFrame(), + 'children', type(self.OnMenuRequest) == "table" and self.OnMenuRequest or function(level, value, valueN_1, valueN_2, valueN_3, valueN_4) + if level == 1 then + if not self.hideMenuTitle then + Dewdrop:AddLine( + 'text', self:GetTitle(), + 'isTitle', true + ) + end + + if self.OnMenuRequest then + self:OnMenuRequest(level, value, false, valueN_1, valueN_2, valueN_3, valueN_4) + end + + if not self.overrideMenu then + if self.MenuSettings and not self.hideMenuTitle then + Dewdrop:AddLine() + end + self:AddImpliedMenuOptions() + end + else + if not self.overrideMenu and self:AddImpliedMenuOptions() then + else + if self.OnMenuRequest then + self:OnMenuRequest(level, value, false, valueN_1, valueN_2, valueN_3, valueN_4) + end + end + end + if level == 1 then + Dewdrop:AddLine( + 'text', CLOSE, + 'tooltipTitle', CLOSE, + 'tooltipText', CLOSE_DESC, + 'func', Dewdrop.Close, + 'arg1', Dewdrop + ) + end + end, + 'point', function(frame) + local x, y = frame:GetCenter() + local leftRight + if x < GetScreenWidth() / 2 then + leftRight = "LEFT" + else + leftRight = "RIGHT" + end + if y < GetScreenHeight() / 2 then + return "BOTTOM" .. leftRight, "TOP" .. leftRight + else + return "TOP" .. leftRight, "BOTTOM" .. leftRight + end + end, + 'dontHook', true + ) + end + if frame == self:GetFrame() then + Dewdrop:Open(self:GetFrame()) + else + Dewdrop:Open(frame, self:GetFrame()) + end +end + +local impliedMenuOptions +function FuBarPlugin:AddImpliedMenuOptions(level) + FuBarPlugin:argCheck(level, 2, "number", "nil") + if not impliedMenuOptions then + impliedMenuOptions = {} + end + if not impliedMenuOptions[self] then + impliedMenuOptions[self] = { type = 'group', args = {} } + Dewdrop:InjectAceOptionsTable(self, impliedMenuOptions[self]) + if impliedMenuOptions[self].args and CheckFuBar() and not self.independentProfile then + impliedMenuOptions[self].args.profile = nil + end + end + return Dewdrop:FeedAceOptionsTable(impliedMenuOptions[self], level and level - 1) +end + +function FuBarPlugin.OnEmbedInitialize(FuBarPlugin, self) + if not self.frame then + local name = "FuBarPlugin" .. self:GetTitle() .. "Frame" + local frame = _G[name] + if not frame or not _G[name .. "Text"] or not _G[name .. "Icon"] then + frame = self:CreateBasicPluginFrame(name) + + local icon = frame:CreateTexture(name .. "Icon", "ARTWORK") + icon:SetWidth(16) + icon:SetHeight(16) + icon:SetPoint("LEFT", frame, "LEFT") + + local text = frame:CreateFontString(name .. "Text", "ARTWORK") + text:SetWidth(134) + text:SetHeight(24) + text:SetPoint("LEFT", icon, "RIGHT", 0, 1) + text:SetFontObject(GameFontNormal) + end + self.frame = frame + self.textFrame = _G[name .. "Text"] + self.iconFrame = _G[name .. "Icon"] + else + self.userDefinedFrame = true + end + + self.frame.plugin = self + self.frame:SetParent(UIParent) + self.frame:SetPoint("RIGHT", UIParent, "LEFT", -5, 0) + self.frame:Hide() + + if self.hasIcon then + self:SetIcon(self.hasIcon) + end + + if CheckFuBar() then + FuBar:RegisterPlugin(self) + end +end + +local CheckShow = function(self, panelId) + if not self.frame:IsShown() and (not self.minimapFrame or not self.minimapFrame:IsShown()) then + self:Show(panelId) + Dewdrop:Refresh(2) + end +end + +local recheckPlugins +function FuBarPlugin.OnEmbedEnable(FuBarPlugin, self) + if not self.userDefinedFrame then + if self:IsIconShown() then + self.iconFrame:Show() + else + self.iconFrame:Hide() + end + end + self:CheckWidth(true) + + if not self.hideWithoutStandby or (self.db and not self.db.profile.hidden) then + if FuBarPlugin.enabledPlugins[self] then + CheckShow(self, self.panelIdTmp) + else + FuBarPlugin:ScheduleEvent(CheckShow, 0, self, self.panelIdTmp) + end + end + FuBarPlugin.enabledPlugins[self] = true + + if not self.overrideTooltip and not self.cannotDetachTooltip and self.db and self.db.profile.detachedTooltip and self.db.profile.detachedTooltip.detached then + FuBarPlugin:ScheduleEvent(self.DetachTooltip, 0, self) + end + + if self:IsLoadOnDemand() and CheckFuBar() then + if not FuBar.db.profile.loadOnDemand then + FuBar.db.profile.loadOnDemand = {} + end + if not FuBar.db.profile.loadOnDemand[FuBarPlugin.folderNames[self] or self.folderName] then + FuBar.db.profile.loadOnDemand[FuBarPlugin.folderNames[self] or self.folderName] = {} + end + FuBar.db.profile.loadOnDemand[FuBarPlugin.folderNames[self] or self.folderName].disabled = nil + end + + if CheckFuBar() and AceLibrary:HasInstance("AceConsole-2.0") then + if not recheckPlugins then + local AceConsole = AceLibrary("AceConsole-2.0") + local AceOO = AceLibrary("AceOO-2.0") + function recheckPlugins() + for k,v in pairs(AceConsole.registry) do + if type(v) == "table" and v.args and AceOO.inherits(v.handler, FuBarPlugin) and not v.handler.independentProfile then + v.args.profile = nil + end + end + end + end + FuBarPlugin:ScheduleEvent(recheckPlugins, 0) + end +end + +function FuBarPlugin.OnEmbedDisable(FuBarPlugin, self) + self:Hide(false) + + if self:IsLoadOnDemand() and CheckFuBar() then + if not FuBar.db.profile.loadOnDemand then + FuBar.db.profile.loadOnDemand = {} + end + if not FuBar.db.profile.loadOnDemand[FuBarPlugin.folderNames[self] or self.folderName] then + FuBar.db.profile.loadOnDemand[FuBarPlugin.folderNames[self] or self.folderName] = {} + end + FuBar.db.profile.loadOnDemand[FuBarPlugin.folderNames[self] or self.folderName].disabled = true + end +end + +function FuBarPlugin.OnEmbedProfileEnable(FuBarPlugin, self) + self:Update() + if self.db and self.db.profile then + if not self.db.profile.detachedTooltip then + self.db.profile.detachedTooltip = {} + end + if Tablet.registry[self.frame] then + Tablet:UpdateDetachedData(self.frame, self.db.profile.detachedTooltip) + else + FuBarPlugin.RegisterTablet(self) + end + local MinimapContainer = AceLibrary(MINIMAPCONTAINER_MAJOR_VERSION) + if MinimapContainer:HasPlugin(self) then + AceLibrary(MINIMAPCONTAINER_MAJOR_VERSION):ReadjustLocation(self) + end + end +end + +function FuBarPlugin.GetAceOptionsDataTable(FuBarPlugin, self) + return { + icon = { + type = "toggle", + name = SHOW_ICON, + desc = SHOW_ICON_DESC, + set = "ToggleIconShown", + get = "IsIconShown", + hidden = function() + return not self.hasIcon or self.hasNoText or self:IsDisabled() or self:IsMinimapAttached() or not self.db + end, + order = -13.7, + handler = self, + }, + text = { + type = "toggle", + name = SHOW_TEXT, + desc = SHOW_TEXT_DESC, + set = "ToggleTextShown", + get = "IsTextShown", + hidden = function() + return self.cannotHideText or not self.hasIcon or self.hasNoText or self:IsDisabled() or self:IsMinimapAttached() or not self.db + end, + order = -13.6, + handler = self, + }, + colorText = { + type = "toggle", + name = SHOW_COLORED_TEXT, + desc = SHOW_COLORED_TEXT_DESC, + set = "ToggleTextColored", + get = "IsTextColored", + hidden = function() + return self.userDefinedFrame or self.hasNoText or self.hasNoColor or self:IsDisabled() or self:IsMinimapAttached() or not self.db + end, + order = -13.5, + handler = self, + }, + detachTooltip = { + type = "toggle", + name = DETACH_TOOLTIP, + desc = DETACH_TOOLTIP_DESC, + get = "IsTooltipDetached", + set = "ToggleTooltipDetached", + hidden = function() + return self.overrideTooltip or self.cannotDetachTooltip or self:IsDisabled() + end, + order = -13.4, + handler = self, + }, + lockTooltip = { + type = "toggle", + name = LOCK_TOOLTIP, + desc = LOCK_TOOLTIP_DESC, + get = function() + return Tablet:IsLocked(self.frame) + end, + set = function() + return Tablet:ToggleLocked(self.frame) + end, + disabled = function() + return not self:IsTooltipDetached() + end, + hidden = function() + return self.overrideTooltip or self.cannotDetachTooltip or self:IsDisabled() + end, + order = -13.3, + handler = self, + }, + position = { + type = "text", + name = POSITION, + desc = POSITION_DESC, + validate = { + LEFT = POSITION_LEFT, + CENTER = POSITION_CENTER, + RIGHT = POSITION_RIGHT + }, + get = function() + return self.panel and self.panel:GetPluginSide(self) + end, + set = function(value) + if self.panel then + self.panel:SetPluginSide(self, value) + end + end, + hidden = function() + return self:IsMinimapAttached() or self:IsDisabled() or not self.panel + end, + order = -13.2, + handler = self, + }, + minimapAttach = { + type = "toggle", + name = ATTACH_TO_MINIMAP, + desc = ATTACH_TO_MINIMAP_DESC, + get = "IsMinimapAttached", + set = "ToggleMinimapAttached", + hidden = function() + return (self.cannotAttachToMinimap and not self:IsMinimapAttached()) or not CheckFuBar() or self:IsDisabled() + end, + order = -13.1, + handler = self, + }, + hide = { + type = "toggle", + cmdName = HIDE_FUBAR_PLUGIN_CMD, + guiName = HIDE_FUBAR_PLUGIN, + desc = HIDE_FUBAR_PLUGIN_DESC, + get = function() + return not self.frame:IsShown() and (not self.minimapFrame or not self.minimapFrame:IsShown()) + end, + set = function() + if not self.frame:IsShown() and (not self.minimapFrame or not self.minimapFrame:IsShown()) then + self:Show() + else + self:Hide() + end + end, + hidden = function() + return not self.hideWithoutStandby or self:IsDisabled() + end, + order = -13, + handler = self, + }, + } +end + +local function activate(self, oldLib, oldDeactivate) + FuBarPlugin = self + + if oldLib then + self.registry = oldLib.registry + self.folderNames = oldLib.folderNames + self.enabledPlugins = oldLib.enabledPlugins + end + + if not self.registry then + self.registry = {} + end + if not self.folderNames then + self.folderNames = {} + end + if not self.enabledPlugins then + self.enabledPlugins = {} + end + + FuBarPlugin.activate(self, oldLib, oldDeactivate) + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +local function external(self, major, instance) + if major == "AceEvent-2.0" then + AceEvent = instance + + AceEvent:embed(self) + elseif major == "Tablet-2.0" then + Tablet = instance + elseif major == "Dewdrop-2.0" then + Dewdrop = instance + end +end + +AceLibrary:Register(FuBarPlugin, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) + +local MinimapContainer = {} + +local function IsMinimapSquare() + return IsAddOnLoaded("CornerMinimap") or IsAddOnLoaded("SquareMinimap") or IsAddOnLoaded("Squeenix") or (IsAddOnLoaded("simpleMinimap") and simpleMinimap:HasModule("skins") and simpleMinimap:GetModule("skins"):GetShape() == "square") +end + +function MinimapContainer:AddPlugin(plugin) + if CheckFuBar() and FuBar:IsChangingProfile() then + return + end + if plugin.panel ~= nil then + plugin.panel:RemovePlugin(plugin) + end + plugin.panel = self + if not plugin.minimapFrame then + local frame = CreateFrame("Button", plugin.frame:GetName() .. "MinimapButton", Minimap) + plugin.minimapFrame = frame + AceLibrary(MAJOR_VERSION).RegisterTablet(plugin) + Tablet:Register(frame, plugin.frame) + frame.plugin = plugin + frame:SetWidth(31) + frame:SetHeight(31) + frame:SetFrameStrata("BACKGROUND") + frame:SetFrameLevel(4) + frame:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") + local icon = frame:CreateTexture(frame:GetName() .. "Icon", "BACKGROUND") + plugin.minimapIcon = icon + local path = plugin:GetIcon() or (plugin.iconFrame and plugin.iconFrame:GetTexture()) or "Interface\\Icons\\INV_Misc_QuestionMark" + icon:SetTexture(path) + if string.sub(path, 1, 16) == "Interface\\Icons\\" then + icon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + icon:SetTexCoord(0, 1, 0, 1) + end + icon:SetWidth(20) + icon:SetHeight(20) + icon:SetPoint("TOPLEFT", frame, "TOPLEFT", 7, -5) + local overlay = frame:CreateTexture(frame:GetName() .. "Overlay","OVERLAY") +overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") + overlay:SetWidth(53) + overlay:SetHeight(53) + overlay:SetPoint("TOPLEFT",frame,"TOPLEFT") + frame:EnableMouse(true) + frame:RegisterForClicks("LeftButtonUp") + frame.plugin = plugin + frame:SetScript("OnClick", function() + if type(plugin.OnClick) == "function" then + if not this.dragged then + plugin:OnClick(arg1) + end + end + end) + frame:SetScript("OnDoubleClick", function() + if type(plugin.OnDoubleClick) == "function" then + plugin:OnDoubleClick(arg1) + end + end) + frame:SetScript("OnReceiveDrag", function() + if type(plugin.OnReceiveDrag) == "function" then + if not this.dragged then + plugin:OnReceiveDrag() + end + end + end) + frame:SetScript("OnMouseDown", function() + this.dragged = false + if arg1 == "LeftButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then + HideDropDownMenu(1) + if type(plugin.OnMouseDown) == "function" then + plugin:OnMouseDown(arg1) + end + elseif arg1 == "RightButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then + plugin:OpenMenu(frame) + else + HideDropDownMenu(1) + if type(plugin.OnMouseDown) == "function" then + plugin:OnMouseDown(arg1) + end + end + if plugin.OnClick or plugin.OnMouseDown or plugin.OnMouseUp or plugin.OnDoubleClick then + if string.sub(this.plugin.minimapIcon:GetTexture(), 1, 16) == "Interface\\Icons\\" then + plugin.minimapIcon:SetTexCoord(0.14, 0.86, 0.14, 0.86) + else + plugin.minimapIcon:SetTexCoord(0.1, 0.9, 0.1, 0.9) + end + end + end) + frame:SetScript("OnMouseUp", function() + if not this.dragged and type(plugin.OnMouseUp) == "function" then + plugin:OnMouseUp(arg1) + end + if string.sub(this.plugin.minimapIcon:GetTexture(), 1, 16) == "Interface\\Icons\\" then + plugin.minimapIcon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + plugin.minimapIcon:SetTexCoord(0, 1, 0, 1) + end + end) + frame:RegisterForDrag("LeftButton") + frame:SetScript("OnDragStart", self.OnDragStart) + frame:SetScript("OnDragStop", self.OnDragStop) + end + plugin.frame:Hide() + plugin.minimapFrame:Show() + self:ReadjustLocation(plugin) + table.insert(self.plugins, plugin) + local exists = false + return true +end + +function MinimapContainer:RemovePlugin(index) + if CheckFuBar() and FuBar:IsChangingProfile() then + return + end + if type(index) == "table" then + index = self:IndexOfPlugin(index) + if not index then + return + end + end + local t = self.plugins + local plugin = t[index] + assert(plugin.panel == self, "Plugin has improper panel field") + plugin:SetPanel(nil) + table.remove(t, index) + return true +end + +function MinimapContainer:ReadjustLocation(plugin) + local frame = plugin.minimapFrame + if plugin.db and plugin.db.profile.minimapPositionWild then + frame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", plugin.db.profile.minimapPositionX, plugin.db.profile.minimapPositionY) + elseif not plugin.db and plugin.minimapPositionWild then + frame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", plugin.minimapPositionX, plugin.minimapPositionY) + else + local position + if plugin.db then + position = plugin.db.profile.minimapPosition or plugin.defaultMinimapPosition or math.random(1, 360) + else + position = plugin.minimapPosition or plugin.defaultMinimapPosition or math.random(1, 360) + end + local angle = math.rad(position or 0) + local x,y + if not IsMinimapSquare() then + x = math.cos(angle) * 80 + y = math.sin(angle) * 80 + else + x = 110 * math.cos(angle) + y = 110 * math.sin(angle) + x = math.max(-82, math.min(x, 84)) + y = math.max(-86, math.min(y, 82)) + end + frame:SetPoint("CENTER", Minimap, "CENTER", x, y) + end +end + +function MinimapContainer:GetPlugin(index) + return self.plugins[index] +end + +function MinimapContainer:GetNumPlugins() + return table.getn(self.plugins) +end + +function MinimapContainer:IndexOfPlugin(plugin) + for i,p in ipairs(self.plugins) do + if p == plugin then + return i, "MINIMAP" + end + end +end + +function MinimapContainer:HasPlugin(plugin) + return self:IndexOfPlugin(plugin) ~= nil +end + +function MinimapContainer:GetPluginSide(plugin) + local index = self:IndexOfPlugin(plugin) + assert(index, "Plugin not in panel") + return "MINIMAP" +end + +function MinimapContainer.OnDragStart() + this.dragged = true + this:LockHighlight() + this:SetScript("OnUpdate", MinimapContainer.OnUpdate) + if string.sub(this.plugin.minimapIcon:GetTexture(), 1, 16) == "Interface\\Icons\\" then + this.plugin.minimapIcon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + this.plugin.minimapIcon:SetTexCoord(0, 1, 0, 1) + end +end + +function MinimapContainer.OnDragStop() + this:SetScript("OnUpdate", nil) + this:UnlockHighlight() +end + +function MinimapContainer.OnUpdate() + if not IsAltKeyDown() then + local mx, my = Minimap:GetCenter() + local px, py = GetCursorPosition() + local scale = UIParent:GetEffectiveScale() + px, py = px / scale, py / scale + local position = math.deg(math.atan2(py - my, px - mx)) + if position <= 0 then + position = position + 360 + elseif position > 360 then + position = position - 360 + end + if this.plugin.db then + this.plugin.db.profile.minimapPosition = position + this.plugin.db.profile.minimapPositionX = nil + this.plugin.db.profile.minimapPositionY = nil + this.plugin.db.profile.minimapPositionWild = nil + else + this.plugin.minimapPosition = position + this.plugin.minimapPositionX = nil + this.plugin.minimapPositionY = nil + this.plugin.minimapPositionWild = nil + end + else + local px, py = GetCursorPosition() + local scale = UIParent:GetEffectiveScale() + px, py = px / scale, py / scale + if this.plugin.db then + this.plugin.db.profile.minimapPositionX = px + this.plugin.db.profile.minimapPositionY = py + this.plugin.db.profile.minimapPosition = nil + this.plugin.db.profile.minimapPositionWild = true + else + this.plugin.minimapPositionX = px + this.plugin.minimapPositionY = py + this.plugin.minimapPosition = nil + this.plugin.minimapPositionWild = true + end + end + MinimapContainer:ReadjustLocation(this.plugin) +end + +local function activate(self, oldLib, oldDeactivate) + MinimapContainer = self + + if oldLib then + self.plugins = oldLib.plugins + end + + if not self.plugins then + self.plugins = {} + end + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(MinimapContainer, MINIMAPCONTAINER_MAJOR_VERSION, MINOR_VERSION, activate) diff --git a/libs/Roster-2.1/Roster-2.1.lua b/libs/Roster-2.1/Roster-2.1.lua new file mode 100644 index 0000000..10eab77 --- /dev/null +++ b/libs/Roster-2.1/Roster-2.1.lua @@ -0,0 +1,425 @@ +--[[ +Name: Roster-2.1 +Revision: $Revision: 21071 $ +X-ReleaseDate: $Date: 2006-12-17 13:56:50 -0500 (Sun, 17 Dec 2006) $ +Author: Maia (maia.proudmoore@gmail.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/wiki/Roster-2.1 +SVN: http://svn.wowace.com/wowace/trunk/RosterLib/ +Description: Party/raid roster management +Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0 +]] + +local MAJOR_VERSION = "Roster-2.1" +local MINOR_VERSION = "$Revision: 21071 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end +if not AceLibrary:HasInstance("AceEvent-2.0") then error(MAJOR_VERSION .. " requires AceEvent-2.0") end + +local unknownUnits = {} +local Lib = {} +local roster +local new, del +do + local cache = setmetatable({}, {__mode='k'}) + function new() + local t = next(cache) + if t then + cache[t] = nil + return t + else + return {} + end + end + + function del(t) + for k in pairs(t) do + t[k] = nil + end + cache[t] = true + return nil + end +end + +------------------------------------------------ +-- activate, enable, disable +------------------------------------------------ + +local function activate(self, oldLib, oldDeactivate) + if oldLib then + if oldLib.roster then self.roster = oldLib.roster end + oldLib:UnregisterAllEvents() + oldLib:CancelAllScheduledEvents() + end + if not self.roster then self.roster = {} end + if oldDeactivate then oldDeactivate(oldLib) end + roster = self.roster +end + + +local function external(self, major, instance) + if major == "AceEvent-2.0" then + instance:embed(self) + if instance:IsFullyInitialized() then + self:AceEvent_FullyInitialized() + else + self:RegisterEvent("AceEvent_FullyInitialized") + end + end +end + +------------------------------------------------ +-- Internal functions +------------------------------------------------ + +function Lib:AceEvent_FullyInitialized() + self:TriggerEvent("RosterLib_Enabled") + + self:RegisterBucketEvent({"RAID_ROSTER_UPDATE", "PARTY_MEMBERS_CHANGED"}, 0.2, "ScanFullRoster") + self:RegisterBucketEvent("UNIT_PET", 0.2, "ScanPet") + + self:ScanFullRoster() +end + + +------------------------------------------------ +-- Unit iterator +------------------------------------------------ + +local UnitIterator +do + local rmem, pmem, step, count + local function SelfIterator() + while step do + local unit + if step == 1 then + -- STEP 1: player + unit = "player" + step = 2 + elseif step == 2 then + -- STEP 2: pet + unit = "pet" + step = nil + end + if unit and UnitExists(unit) then return unit end + end + end + + local function SelfAndPartyIterator() + while step do + local unit + if step <= 2 then + unit = SelfIterator() + if not step then step = 3 end + elseif step == 3 then + -- STEP 3: party units + unit = string.format("party%d", count) + step = 4 + elseif step == 4 then + -- STEP 4: party pets + unit = string.format("partypet%d", count) + count = count + 1 + step = count <= pmem and 3 or nil + end + if unit and UnitExists(unit) then return unit end + end + end + + local function RaidIterator() + while step do + local unit + if step == 1 then + -- STEP 1: raid units + unit = string.format("raid%d", count) + step = 2 + elseif step == 2 then + -- STEP 2: raid pets + unit = string.format("raidpet%d", count) + count = count + 1 + step = count <= rmem and 1 or nil + end + if unit and UnitExists(unit) then return unit end + end + end + + function UnitIterator() + rmem = GetNumRaidMembers() + step = 1 + if rmem == 0 then + pmem = GetNumPartyMembers() + if pmem == 0 then + return SelfIterator, false + else + count = 1 + return SelfAndPartyIterator, false + end + else + count = 1 + return RaidIterator, true + end + end +end + +------------------------------------------------ +-- Roster code +------------------------------------------------ + +local rosterScanCache = {} +function Lib:ScanFullRoster() + local changed + local it, isInRaid = UnitIterator() + -- save all units we currently have, this way we can check who to remove from roster later. + for name in pairs(roster) do + rosterScanCache[name] = true + end + -- update data + for unitid in it do + local name = self:CreateOrUpdateUnit(unitid, isInRaid) + -- we successfully added a unit, so we don't need to remove it next step + if name then + rosterScanCache[name] = nil + changed = true + end + end + -- clear units we had in roster that either left the raid or are unknown for some reason. + for name in pairs(rosterScanCache) do + self:RemoveUnit(name) + rosterScanCache[name] = nil + changed = true + end + self:ProcessRoster() + if changed then + self:TriggerEvent("RosterLib_RosterUpdated") + end +end + + + +function Lib:ScanPet(owner_list) + local changed + for owner in pairs(owner_list) do + local unitid = self:GetPetFromOwner(owner) + if not unitid then return end + + if not UnitExists(unitid) then + unknownUnits[unitid] = nil + -- find the pet in the roster we need to delete + for _,u in pairs(roster) do + if u.unitid == unitid then + self:RemoveUnit(u.name) + changed = true + end + end + else + _, changed = self:CreateOrUpdateUnit(unitid) + end + self:ProcessRoster() + end + if changed then + self:TriggerEvent("RosterLib_RosterUpdated") + end +end + + +function Lib:GetPetFromOwner(id) + -- convert party3 crap to raid IDs when in raid. + local owner = self:GetUnitIDFromUnit(id) + if not owner then return end + + -- get ID + if owner:find("raid") then + return owner:gsub("raid", "raidpet") + elseif owner:find("party") then + return owner:gsub("party", "partypet") + elseif owner == "player" then + return "pet" + else + return nil + end +end + + +function Lib:ScanUnknownUnits() + local changed + for unitid in pairs(unknownUnits) do + local name, c + if UnitExists(unitid) then + name, c = self:CreateOrUpdateUnit(unitid) + else + unknownUnits[unitid] = nil + end + -- some pets never have a name. too bad for them, farewell! + if not name and unitid:find("pet") then + unknownUnits[unitid] = nil + else + changed = changed or c + end + end + self:ProcessRoster() + if changed then + self:TriggerEvent("RosterLib_RosterUpdated") + end +end + + +function Lib:ProcessRoster() + if next(unknownUnits, nil) then + self:CancelScheduledEvent("ScanUnknownUnits") + self:ScheduleEvent("ScanUnknownUnits", self.ScanUnknownUnits, 1, self) + end +end + + +function Lib:CreateOrUpdateUnit(unitid, isInRaid) + -- check for name + local name = UnitName(unitid) + if name and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING and not UnitIsCharmed(unitid) then + local unit = roster[name] + local isPet = unitid:find("pet") + + -- clear stuff + unknownUnits[unitid] = nil + -- return if a pet attempts to replace a player name + -- this doesnt fix the problem with 2 pets overwriting each other FIXME + if isPet and unit and unit.class ~= "PET" then + return name + end + -- save old data if existing + local old_name, old_unitid, old_class, old_rank, old_subgroup, old_online + if unit then + old_name = unit.name + old_unitid = unit.unitid + old_class = unit.class + old_rank = unit.rank + old_subgroup = unit.subgroup + old_online = unit.online + else + -- object + unit = new() + roster[name] = unit + end + + local new_name, new_unitid, new_class, new_rank, new_subgroup, new_online + + -- name + new_name = name + -- unitid + new_unitid = unitid + -- class + if isPet then + new_class = "PET" + else + _, new_class = UnitClass(unitid) + end + if isInRaid == nil and GetNumRaidMembers() > 0 then + isInRaid = true + end + + -- subgroup and rank + new_subgroup = 1 + new_rank = 0 -- XXX Support party leader? + if isInRaid then + local _,_,num = unitid:find("(%d+)") + if num then + _, new_rank, new_subgroup = GetRaidRosterInfo(num) + end + end + -- online/offline status + new_online = UnitIsConnected(unitid) + + -- compare data + if not old_name + or new_name ~= old_name + or new_unitid ~= old_unitid + or new_class ~= old_class + or new_subgroup ~= old_subgroup + or new_rank ~= old_rank + or new_online ~= old_online + then + unit.name = new_name + unit.unitid = new_unitid + unit.class = new_class + unit.subgroup = new_subgroup + unit.rank = new_rank + unit.online = new_online + self:TriggerEvent("RosterLib_UnitChanged", + new_unitid, new_name, new_class, new_subgroup, new_rank, + old_name, old_unitid, old_class, old_subgroup, old_rank) + return name, true + end + return name + else + unknownUnits[unitid] = true + return false + end +end + + +function Lib:RemoveUnit(name) + local r = roster[name] + roster[name] = nil + self:TriggerEvent("RosterLib_UnitChanged", + nil, nil, nil, nil, nil, + r.name, r.unitid, r.class, r.subgroup, r.rank) + r = del(r) +end + + +------------------------------------------------ +-- API +------------------------------------------------ + +function Lib:GetUnitIDFromName(name) + if roster[name] then + return roster[name].unitid + else + return nil + end +end + + +function Lib:GetUnitIDFromUnit(unit) + local name = UnitName(unit) + if name and roster[name] then + return roster[name].unitid + else + return nil + end +end + + +function Lib:GetUnitObjectFromName(name) + return roster[name] +end + + +function Lib:GetUnitObjectFromUnit(unit) + local name = UnitName(unit) + return roster[name] +end + + +local function iter(t) + local key = t.key + local pets = t.pets + repeat + key = next(roster, key) + if not key then + t = del(t) + return nil + end + until (pets or roster[key].class ~= "PET") + t.key = key + return roster[key] +end + +function Lib:IterateRoster(pets) + local t = new() + t.pets = pets + return iter, t +end + +AceLibrary:Register(Lib, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) + diff --git a/libs/Tablet-2.0/Tablet-2.0.lua b/libs/Tablet-2.0/Tablet-2.0.lua new file mode 100644 index 0000000..d9865a7 --- /dev/null +++ b/libs/Tablet-2.0/Tablet-2.0.lua @@ -0,0 +1,2948 @@ +--[[ +Name: Tablet-2.0 +Revision: $Rev: 23368 $ +Author(s): ckknight (ckknight@gmail.com) +Website: http://ckknight.wowinterface.com/ +Documentation: http://www.wowace.com/index.php/Tablet-2.0 +SVN: http://svn.wowace.com/wowace/trunk/TabletLib/Tablet-2.0 +Description: A library to provide an efficient, featureful tooltip-style display. +Dependencies: AceLibrary, (optional) Dewdrop-2.0 +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "Tablet-2.0" +local MINOR_VERSION = "$Revision: 23368 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +local DEBUG = false + +local SCROLL_UP = "Scroll up" +local SCROLL_DOWN = "Scroll down" +local HINT = "Hint" +local DETACH = "Detach" +local DETACH_DESC = "Detach the tablet from its source." +local SIZE = "Size" +local SIZE_DESC = "Scale the tablet." +local CLOSE_MENU = "Close menu" +local CLOSE_MENU_DESC = "Close the menu." +local COLOR = "Background color" +local COLOR_DESC = "Set the background color." +local LOCK = "Lock" +local LOCK_DESC = "Lock the tablet in its current position. Alt+Right-click for menu or Alt+drag to drag it when locked." + +if GetLocale() == "deDE" then + SCROLL_UP = "Hochscrollen" + SCROLL_DOWN = "Runterscrollen" + HINT = "Hinweis" + DETACH = "L\195\182sen" + DETACH_DESC = "L\195\182st den Tooltip aus seiner Verankerung." + SIZE = "Gr\195\182\195\159e" + SIZE_DESC = "Gr\195\182\195\159e des Tooltips \195\164ndern." + CLOSE_MENU = "Menu schlie\195\159en" + CLOSE_MENU_DESC = "Schlie\195\159t das Menu." + COLOR = "Hintergrundfarbe" + COLOR_DESC = "Hintergrundfarbe setzen." + LOCK = "Sperren" + LOCK_DESC = "Sperrt die aktuelle Position vom Tooltip. Alt+Rechts-klick f\195\188rs Men\195\188 oder Alt+Verschieben f\195\188rs verschieben wenn es gesperrt ist." +elseif GetLocale() == "koKR" then + SCROLL_UP = "위로 스크롤" + SCROLL_DOWN = "아래로 스크롤" + HINT = "힌트" + DETACH = "분리" + DETACH_DESC = "테이블을 분리합니다." + SIZE = "크기" + SIZE_DESC = "테이블의 크기입니다." + CLOSE_MENU = "메뉴 닫기" + CLOSE_MENU_DESC = "메뉴를 닫습니다." + COLOR = "배경 색상" + COLOR_DESC = "배경 색상을 설정합니다." + LOCK = "고정" + LOCK_DESC = "현재 위치에 테이블을 고정합니다. 알트+우클릭 : 메뉴열기, 알트+드래그 : 고정된것을 드래그합니다." +elseif GetLocale() == "zhCN" then + SCROLL_UP = "向上翻转" + SCROLL_DOWN = "向上翻转" + HINT = "提示" + DETACH = "分离" + DETACH_DESC = "分离菜单为独立提示." + SIZE = "尺寸" + SIZE_DESC = "缩放菜单显示尺寸." + CLOSE_MENU = "关闭菜单" + CLOSE_MENU_DESC = "关闭菜单" + COLOR = "背景颜色" + COLOR_DESC = "设置菜单背景颜色." + LOCK = "锁定" + LOCK_DESC = "锁定菜单当前位置. alt+右键 将显示选项, alt+拖动 可以移动已锁定的菜单." +elseif GetLocale() == "zhTW" then + SCROLL_UP = "向上翻轉" + SCROLL_DOWN = "向上翻轉" + HINT = "提示" + DETACH = "分離" + DETACH_DESC = "分離選單為獨立提示。" + SIZE = "尺寸" + SIZE_DESC = "縮放選單顯示尺寸。" + CLOSE_MENU = "關閉選單" + CLOSE_MENU_DESC = "關閉選單" + COLOR = "背景顏色" + COLOR_DESC = "設置選單背景顏色。" + LOCK = "鎖定" + LOCK_DESC = "鎖定選單目前位置. Alt+右鍵 將顯示選項,Alt+拖動 可以移動已鎖定的選單。" +elseif GetLocale() == "frFR" then + SCROLL_UP = "Parcourir vers le haut" + SCROLL_DOWN = "Parcourir vers le bas" + HINT = "Astuce" + DETACH = "D\195\169tacher" + DETACH_DESC = "Permet de d\195\169tacher le tableau de sa source." + SIZE = "Taille" + SIZE_DESC = "Permet de changer l'\195\169chelle du tableau." + CLOSE_MENU = "Fermer le menu" + CLOSE_MENU_DESC = "Ferme ce menu." + COLOR = "Couleur du fond" + COLOR_DESC = "Permet de d\195\169finir la couleur du fond." + LOCK = "Bloquer" + LOCK_DESC = "Bloque le tableau \195\160 sa position actuelle. Alt+clic-droit pour le menu ou Alt+glisser pour le d\195\169placer quand il est bloqu\195\169." +elseif GetLocale() == "esES" then + SCROLL_UP = "Desplazar hacia arriba" + SCROLL_DOWN = "Desplazar hacia abajo" + HINT = "Consejo" + DETACH = "Separar" + DETACH_DESC = "Separa el tooltip de su fuente." + SIZE = "Tama\195\177o" + SIZE_DESC = "Escala el tooltip" + CLOSE_MENU = "Cerrar men\195\186" + CLOSE_MENU_DESC = "Cierra el men\195\186" + COLOR = "Color de fondo" + COLOR_DESC = "Establece el color de fondo" + LOCK = "Bloquear" + LOCK_DESC = "Bloquea el tooltip en su posici\195\179n actual. Clic+Alt para el men\195\186 y arrastra+Alt para arrastrarlo cuando est\195\161 bloqueado" +end + +local start = GetTime() +local wrap +local GetProfileInfo +if DEBUG then + local tree = {} + local treeMemories = {} + local treeTimes = {} + local memories = {} + local times = {} + function wrap(value, name) + if type(value) == "function" then + local oldFunction = value + memories[name] = 0 + times[name] = 0 + return function(self, ...) + local pos = #tree + table.insert(tree, name) + table.insert(treeMemories, 0) + table.insert(treeTimes, 0) + local t, mem = GetTime(), gcinfo() + local r1, r2, r3, r4, r5, r6, r7, r8 = oldFunction(self, ...) + mem, t = gcinfo() - mem, GetTime() - t + if pos > 0 then + treeMemories[pos] = treeMemories[pos] + mem + treeTimes[pos] = treeTimes[pos] + t + end + local otherMem = table.remove(treeMemories) + if mem - otherMem > 0 then + memories[name] = memories[name] + mem - otherMem + end + times[name] = times[name] + t - table.remove(treeTimes) + table.remove(tree) + return r1, r2, r3, r4, r5, r6, r7, r8 + end + end + end + + function GetProfileInfo() + return GetTime() - start, times, memories + end +else + function wrap(value) + return value + end +end + +local MIN_TOOLTIP_SIZE = 200 +local TESTSTRING_EXTRA_WIDTH = 8 +local Tablet = {} +local Dewdrop = nil +local CleanCategoryPool +local pool = {} + +local function del(t) + if t then + for k in pairs(t) do + t[k] = nil + end + setmetatable(t, nil) + pool[t] = true + end +end + +local new + +local function copy(parent) + local t = next(pool) or {} + pool[t] = nil + if parent then + for k,v in pairs(parent) do + t[k] = v + end + setmetatable(t, getmetatable(parent)) + end + return t +end + +function new(...) + local t = next(pool) or {} + pool[t] = nil + + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t +end + +local tmp +tmp = setmetatable({}, {__index = function(self, key) + local t = {} + tmp[key] = function(...) + for k in pairs(t) do + t[k] = nil + end + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t + end + return tmp[key] +end}) + +local headerSize, normalSize +if GameTooltipHeaderText then + _,headerSize = GameTooltipHeaderText:GetFont() +else + headerSize = 14 +end +if GameTooltipText then + _,normalSize = GameTooltipText:GetFont() +else + normalSize = 12 +end +local tooltip +local testString-- = CreateFrame("Frame", UIParent):CreateFontString(nil, "OVERLAY") +local TabletData = {} +local Category = {} +local Line = {} +local function getTestWidth(font, size, text) + testString:SetWidth(0) + testString:SetFontObject(font) + local a,_,b = testString:GetFont() + testString:SetFont(a, size, b) + testString:SetText(text) + return testString:GetStringWidth()-- + TESTSTRING_EXTRA_WIDTH +end +do + local TabletData_mt = { __index = TabletData } + function TabletData:new(tablet) + local self = new() + self.categories = new() + self.id = 0 + self.width = 0 -- (MIN_TOOLTIP_SIZE - 20)*tablet.fontSizePercent + self.tablet = tablet + self.title = "Title" + self.titleR, self.titleG, self.titleB = nil, nil, nil + self.num_lines = 0 + setmetatable(self, TabletData_mt) + return self + end + + function TabletData:checkMinWidth() + local width = (MIN_TOOLTIP_SIZE - 20)*self.tablet.fontSizePercent + if self.width < width then + self.width = width + end + end + + function TabletData:del() + for k, v in ipairs(self.categories) do + v:del() + end + del(self.categories) + del(self) + end + + function TabletData:Display() + if self.tablet == tooltip or self.tablet.registration.showTitleWhenDetached then + local info = new( + 'hideBlankLine', true, + 'text', self.title, + 'justify', "CENTER", + 'font', GameTooltipHeaderText, + 'isTitle', true + ) + if self.titleR then + info.textR = self.titleR + info.textG = self.titleG + info.textB = self.titleB + end + self:AddCategory(info, 1) + del(info) + end + if self.tablet == tooltip or self.tablet.registration.showHintWhenDetached then + if self.hint then + self:AddCategory(nil):AddLine( + 'text', HINT .. ": " .. self.hint, + 'textR', 0, + 'textG', 1, + 'textB', 0, + 'wrap', true + ) + end + end + + local tabletData = self.tabletData + for k, v in ipairs(self.categories) do + local width + if v.columns <= 2 then + width = v.x1 + else + width = v.x1 + v.x2 + v.x3 + v.x4 + v.x5 + v.x6 + (v.columns - 1) * 20 + end + if self.width < width then + self.width = width + end + end + + local good = false + local lastTitle = true + for k, v in ipairs(self.categories) do + if lastTitle then + v.hideBlankLine = true + lastTitle = false + end + if v:Display(self.tablet) and (not v.isTitle or not self.tablet.registration.hideWhenEmpty or next(self.categories, k)) then + good = true + end + if v.isTitle then + lastTitle = true + end + end + if not good then + if self.tablet == tooltip or not self.tablet.registration.hideWhenEmpty then + local width + local info = new( + 'hideBlankLine', true, + 'text', self.title, + 'justify', "CENTER", + 'font', GameTooltipHeaderText, + 'isTitle', true + ) + local cat = self:AddCategory(info) + del(info) + self.width = self.categories[#self.categories].x1 + cat:Display(self.tablet) + else + self.tablet:__Hide() + self.tablet.tmpHidden = true + end + else + self.tablet:__Show() + self.tablet.tmpHidden = nil + end + end + + function TabletData:AddCategory(info, index) + local made = false + if not info then + made = true + info = new() + end + local cat = Category:new(self, info) + if index then + table.insert(self.categories, index, cat) + else + table.insert(self.categories, cat) + end + if made then + del(info) + end + return cat + end + + function TabletData:SetHint(hint) + self.hint = hint + end + + function TabletData:SetTitle(title) + self.title = title or "Title" + end + + function TabletData:SetTitleColor(r, g, b) + self.titleR = r + self.titleG = g + self.titleB = b + end +end +do + local Category_mt = { __index = Category } + function Category:new(tabletData, info, superCategory) + local self = copy(info) + if superCategory and not self.noInherit then + self.superCategory = superCategory.superCategory + for k, v in pairs(superCategory) do + if string.find(k, "^child_") then + local k = strsub(k, 7) + if self[k] == nil then + self[k] = v + end + end + end + self.columns = superCategory.columns + else + self.superCategory = self + end + self.tabletData = tabletData + self.lines = new() + if not self.columns then + self.columns = 1 + end + self.x1 = 0 + self.x2 = 0 + self.x3 = 0 + self.x4 = 0 + self.x5 = 0 + self.x6 = 0 + setmetatable(self, Category_mt) + self.lastWasTitle = nil + if self.text or self.text2 or self.text3 or self.text4 or self.text5 or self.text6 then + local x = new( + 'category', category, + 'text', self.text, + 'textR', self.textR or 1, + 'textG', self.textG or 1, + 'textB', self.textB or 1, + 'fakeChild', true, + 'func', self.func, + 'hasCheck', self.hasCheck, + 'checked', self.checked, + 'checkIcon', self.checkIcon, + 'isRadio', self.isRadio, + 'font', self.font, + 'size', self.size, + 'wrap', self.wrap, + 'catStart', true, + 'indentation', self.indentation, + 'noInherit', true, + 'justify', self.justify, + 'justify2', self.justify2, + 'justify3', self.justify3, + 'justify4', self.justify4, + 'justify5', self.justify5, + 'justify6', self.justify6 + ) + local i = 1 + while true do + local k = 'arg' .. i + local v = self[k] + if v == nil then + break + end + x[k] = v + i = i + 1 + end + if self.isTitle then + x.textR = self.textR or 1 + x.textG = self.textG or 0.823529 + x.textB = self.textB or 0 + else + x.textR = self.textR or 1 + x.textG = self.textG or 1 + x.textB = self.textB or 1 + end + x.text2 = self.text2 + x.text3 = self.text3 + x.text4 = self.text4 + x.text5 = self.text5 + x.text6 = self.text6 + x.text2R = self.text2R or self.textR2 or 1 + x.text2G = self.text2G or self.textG2 or 1 + x.text2B = self.text2B or self.textB2 or 1 + x.text3R = self.text3R or self.textR3 or 1 + x.text3G = self.text3G or self.textG3 or 1 + x.text3B = self.text3B or self.textB3 or 1 + x.text4R = self.text4R or self.textR4 or 1 + x.text4G = self.text4G or self.textG4 or 1 + x.text4B = self.text4B or self.textB4 or 1 + x.text5R = self.text5R or self.textR5 or 1 + x.text5G = self.text5G or self.textG5 or 1 + x.text5B = self.text5B or self.textB5 or 1 + x.text6R = self.text6R or self.textR6 or 1 + x.text6G = self.text6G or self.textG6 or 1 + x.text6B = self.text6B or self.textB6 or 1 + x.font2 = self.font2 + x.font3 = self.font3 + x.font4 = self.font4 + x.font5 = self.font5 + x.font6 = self.font6 + x.size2 = self.size2 + x.size3 = self.size3 + x.size4 = self.size4 + x.size5 = self.size5 + x.size6 = self.size6 + self:AddLine(x) + del(x) + self.lastWasTitle = true + end + return self + end + + function Category:del() + local prev = garbageLine + for k, v in pairs(self.lines) do + v:del() + end + del(self.lines) + del(self) + end + + function Category:AddLine(...) + self.lastWasTitle = nil + local line + local k1 = ... + if type(k1) == "table" then + local k2 = select(2, ...) + Line:new(self, k1, k2) + else + local info = new(...) + Line:new(self, info) + info = del(info) + end + end + + function Category:AddCategory(...) + local lastWasTitle = self.lastWasTitle + self.lastWasTitle = nil + local info + local k1 = ... + if type(k1) == "table" then + info = k1 + else + info = new(...) + end + if lastWasTitle or #self.lines == 0 then + info.hideBlankLine = true + end + local cat = Category:new(self.tabletData, info, self) + table.insert(self.lines, cat) + if info ~= k1 then + info = del(info) + end + return cat + end + + function Category:HasChildren() + local hasChildren = false + for k, v in ipairs(self.lines) do + if v.HasChildren then + if v:HasChildren() then + return true + end + end + if not v.fakeChild then + return true + end + end + return false + end + + local lastWasTitle = false + function Category:Display(tablet) + if not self.isTitle and not self.showWithoutChildren and not self:HasChildren() then + return false + end + if not self.hideBlankLine and not lastWasTitle then + local info = new( + 'blank', true, + 'fakeChild', true + ) + self:AddLine(info, 1) + del(info) + end + local good = false + if #self.lines > 0 then + self.tabletData.id = self.tabletData.id + 1 + self.id = self.tabletData.id + for k, v in ipairs(self.lines) do + if v:Display(tablet) then + good = true + end + end + end + lastWasTitle = self.isTitle + return good + end +end +do + local Line_mt = { __index = Line } + function Line:new(category, info, position) + local self = copy(info) + if not info.noInherit then + for k, v in pairs(category) do + if string.find(k, "^child_") then + local k = strsub(k, 7) + if self[k] == nil then + self[k] = v + end + end + end + end + self.category = category + if position then + table.insert(category.lines, position, self) + else + table.insert(category.lines, self) + end + setmetatable(self, Line_mt) + local n = category.tabletData.num_lines + 1 + category.tabletData.num_lines = n + if n == 15 then + category.tabletData:checkMinWidth() + end + local columns = category.columns + if columns == 1 then + if not self.justify then + self.justify = "LEFT" + end + elseif columns == 2 then + self.justify = "LEFT" + self.justify2 = "RIGHT" + if self.wrap then + self.wrap2 = false + end + elseif columns == 3 then + if not self.justify then + self.justify = "LEFT" + end + if not self.justify2 then + self.justify2 = "CENTER" + end + if not self.justify3 then + self.justify3 = "RIGHT" + end + if self.wrap then + self.wrap2 = false + self.wrap3 = false + elseif self.wrap2 then + self.wrap3 = false + end + elseif columns == 4 then + if not self.justify then + self.justify = "LEFT" + end + if not self.justify2 then + self.justify2 = "CENTER" + end + if not self.justify3 then + self.justify3 = "CENTER" + end + if not self.justify4 then + self.justify4 = "RIGHT" + end + if self.wrap then + self.wrap2 = false + self.wrap3 = false + self.wrap4 = false + elseif self.wrap2 then + self.wrap3 = false + self.wrap4 = false + elseif self.wrap3 then + self.wrap4 = false + end + elseif columns == 5 then + if not self.justify then + self.justify = "LEFT" + end + if not self.justify2 then + self.justify2 = "CENTER" + end + if not self.justify3 then + self.justify3 = "CENTER" + end + if not self.justify4 then + self.justify4 = "CENTER" + end + if not self.justify5 then + self.justify5 = "RIGHT" + end + if self.wrap then + self.wrap2 = false + self.wrap3 = false + self.wrap4 = false + self.wrap5 = false + elseif self.wrap2 then + self.wrap3 = false + self.wrap4 = false + self.wrap5 = false + elseif self.wrap3 then + self.wrap4 = false + self.wrap5 = false + elseif self.wrap4 then + self.wrap5 = false + end + elseif columns == 6 then + if not self.justify then + self.justify = "LEFT" + end + if not self.justify2 then + self.justify2 = "CENTER" + end + if not self.justify3 then + self.justify3 = "CENTER" + end + if not self.justify4 then + self.justify4 = "CENTER" + end + if not self.justify5 then + self.justify5 = "CENTER" + end + if not self.justify6 then + self.justify6 = "RIGHT" + end + if self.wrap then + self.wrap2 = false + self.wrap3 = false + self.wrap4 = false + self.wrap5 = false + self.wrap6 = false + elseif self.wrap2 then + self.wrap3 = false + self.wrap4 = false + self.wrap5 = false + self.wrap6 = false + elseif self.wrap3 then + self.wrap4 = false + self.wrap5 = false + self.wrap6 = false + elseif self.wrap4 then + self.wrap5 = false + self.wrap6 = false + elseif self.wrap5 then + self.wrap6 = false + end + end + if self.textR2 then + self.text2R, self.textR2 = self.text2R or self.textR2 + self.text2G, self.textG2 = self.text2G or self.textG2 + self.text2B, self.textB2 = self.text2B or self.textB2 + if self.textR3 then + self.text3R, self.textR3 = self.text3R or self.textR3 + self.text3G, self.textG3 = self.text3G or self.textG3 + self.text3B, self.textB3 = self.text3B or self.textB3 + if self.textR4 then + self.text4R, self.textR4 = self.text4R or self.textR4 + self.text4G, self.textG4 = self.text4G or self.textG4 + self.text4B, self.textB4 = self.text4B or self.textB4 + if self.textR5 then + self.text5R, self.textR5 = self.text5R or self.textR5 + self.text5G, self.textG5 = self.text5G or self.textG5 + self.text5B, self.textB5 = self.text5B or self.textB5 + if self.textR5 then + self.text6R, self.textR6 = self.text6R or self.textR6 + self.text6G, self.textG6 = self.text6G or self.textG6 + self.text6B, self.textB6 = self.text6B or self.textB6 + end + end + end + end + end + if not self.indentation or self.indentation < 0 then + self.indentation = 0 + end + if not self.font then + self.font = GameTooltipText + end + if not self.font2 then + self.font2 = self.font + end + if not self.font3 then + self.font3 = self.font + end + if not self.font4 then + self.font4 = self.font + end + if not self.font5 then + self.font5 = self.font + end + if not self.font6 then + self.font6 = self.font + end + if not self.size then + _,self.size = self.font:GetFont() + end + if not self.size2 then + _,self.size2 = self.font2:GetFont() + end + if not self.size3 then + _,self.size3 = self.font3:GetFont() + end + if not self.size4 then + _,self.size4 = self.font4:GetFont() + end + if not self.size5 then + _,self.size5 = self.font5:GetFont() + end + if not self.size6 then + _,self.size6 = self.font6:GetFont() + end + + local fontSizePercent = category.tabletData.tablet.fontSizePercent + local w = 0 + self.checkWidth = 0 + testString = category.tabletData.tablet.lefts[1] + if self.text then + if not self.wrap then + local testWidth = getTestWidth(self.font, self.size * fontSizePercent, self.text) + local checkWidth = self.hasCheck and self.size * fontSizePercent or 0 + self.checkWidth = checkWidth + w = testWidth + self.indentation * fontSizePercent + checkWidth + if category.superCategory.x1 < w then + category.superCategory.x1 = w + end + else + if columns == 1 then + local testWidth = getTestWidth(self.font, self.size * fontSizePercent, self.text) + local checkWidth = self.hasCheck and self.size * fontSizePercent or 0 + self.checkWidth = checkWidth + w = testWidth + self.indentation * fontSizePercent + checkWidth + if w > (MIN_TOOLTIP_SIZE - 20) * fontSizePercent then + w = (MIN_TOOLTIP_SIZE - 20) * fontSizePercent + end + else + w = MIN_TOOLTIP_SIZE * fontSizePercent / 2 + end + if category.superCategory.x1 < w then + category.superCategory.x1 = w + end + end + end + if columns == 2 and self.text2 then + if not self.wrap2 then + local testWidth = getTestWidth(self.font2, self.size2 * fontSizePercent, self.text2) + w = w + 40 * fontSizePercent + testWidth + if category.superCategory.x1 < w then + category.superCategory.x1 = w + end + else + w = w + 40 * fontSizePercent + MIN_TOOLTIP_SIZE * fontSizePercent / 2 + if category.superCategory.x1 < w then + category.superCategory.x1 = w + end + end + elseif columns >= 3 then + if self.text2 then + if not self.wrap2 then + local testWidth = getTestWidth(self.font2, self.size2 * fontSizePercent, self.text2) + local w = testWidth + if category.superCategory.x2 < w then + category.superCategory.x2 = w + end + else + local w = MIN_TOOLTIP_SIZE / 2 + if category.superCategory.x2 < w then + category.superCategory.x2 = w + end + end + end + if self.text3 then + if not self.wrap3 then + local testWidth = getTestWidth(self.font3, self.size3 * fontSizePercent, self.text3) + local w = testWidth + if category.superCategory.x3 < w then + category.superCategory.x3 = w + end + else + local w = MIN_TOOLTIP_SIZE / 2 + if category.superCategory.x3 < w then + category.superCategory.x3 = w + end + end + end + if columns >= 4 then + if self.text4 then + if not self.wrap4 then + local testWidth = getTestWidth(self.font4, self.size4 * fontSizePercent, self.text4) + w = testWidth + if category.superCategory.x4 < w then + category.superCategory.x4 = w + end + else + local w = MIN_TOOLTIP_SIZE / 2 + if category.superCategory.x4 < w then + category.superCategory.x4 = w + end + end + end + if columns >= 5 then + if self.text5 then + if not self.wrap5 then + local testWidth = getTestWidth(self.font5, self.size5 * fontSizePercent, self.text5) + w = testWidth + if category.superCategory.x5 < w then + category.superCategory.x5 = w + end + else + local w = MIN_TOOLTIP_SIZE / 2 + if category.superCategory.x5 < w then + category.superCategory.x5 = w + end + end + end + if columns >= 6 then + if self.text6 then + if not self.wrap6 then + local testWidth = getTestWidth(self.font6, self.size6 * fontSizePercent, self.text6) + w = testWidth + if category.superCategory.x6 < w then + category.superCategory.x6 = w + end + else + local w = MIN_TOOLTIP_SIZE / 2 + if category.superCategory.x6 < w then + category.superCategory.x6 = w + end + end + end + end + end + end + end + return self + end + + function Line:del() + del(self) + end + + function Line:Display(tablet) + tablet:AddLine(self) + return true + end +end + +local function button_OnEnter() + if type(this.self:GetScript("OnEnter")) == "function" then + this.self:GetScript("OnEnter")() + end + this.highlight:Show() +end + +local function button_OnLeave() + if type(this.self:GetScript("OnLeave")) == "function" then + this.self:GetScript("OnLeave")() + end + this.highlight:Hide() +end + +local num_buttons = 0 +local function NewLine(self) + if self.maxLines <= self.numLines then + self.maxLines = self.maxLines + 1 + num_buttons = num_buttons + 1 + local button = CreateFrame("Button", "Tablet20Button" .. num_buttons, self) + button:SetFrameLevel(self:GetFrameLevel()+2) + button.indentation = 0 + local check = button:CreateTexture(nil, "ARTWORK") + local left = button:CreateFontString(nil, "ARTWORK") + testString = left + local right = button:CreateFontString(nil, "ARTWORK") + local third = button:CreateFontString(nil, "ARTWORK") + local fourth = button:CreateFontString(nil, "ARTWORK") + local fifth = button:CreateFontString(nil, "ARTWORK") + local sixth = button:CreateFontString(nil, "ARTWORK") + local highlight = button:CreateTexture(nil, "BACKGROUND") + highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + button.highlight = highlight + highlight:SetBlendMode("ADD") + highlight:SetAllPoints(button) + highlight:Hide() + table.insert(self.buttons, button) + table.insert(self.checks, check) + table.insert(self.lefts, left) + table.insert(self.rights, right) + table.insert(self.thirds, third) + table.insert(self.fourths, fourth) + table.insert(self.fifths, fifth) + table.insert(self.sixths, sixth) + left:SetWidth(0) + if self.maxLines == 1 then + left:SetFontObject(GameTooltipHeaderText) + right:SetFontObject(GameTooltipHeaderText) + third:SetFontObject(GameTooltipHeaderText) + fourth:SetFontObject(GameTooltipHeaderText) + fifth:SetFontObject(GameTooltipHeaderText) + sixth:SetFontObject(GameTooltipHeaderText) + left:SetJustifyH("CENTER") + button:SetPoint("TOPLEFT", self, "TOPLEFT", 8, -10) + else + left:SetFontObject(GameTooltipText) + right:SetFontObject(GameTooltipText) + third:SetFontObject(GameTooltipText) + fourth:SetFontObject(GameTooltipText) + fifth:SetFontObject(GameTooltipText) + sixth:SetFontObject(GameTooltipText) + button:SetPoint("TOPLEFT", self.buttons[self.maxLines - 1], "BOTTOMLEFT", 0, -2) + end + button:SetScript("OnEnter", button_OnEnter) + button:SetScript("OnLeave", button_OnLeave) + button.check = check + button.self = self + button:SetPoint("RIGHT", self, "RIGHT", -12, 0) + check.shown = false + check:SetPoint("TOPLEFT", button, "TOPLEFT") + left:SetPoint("TOPLEFT", check, "TOPLEFT") + right:SetPoint("TOPLEFT", left, "TOPRIGHT", 40 * self.fontSizePercent, 0) + third:SetPoint("TOPLEFT", right, "TOPRIGHT", 20 * self.fontSizePercent, 0) + fourth:SetPoint("TOPLEFT", third, "TOPRIGHT", 20 * self.fontSizePercent, 0) + fifth:SetPoint("TOPLEFT", fourth, "TOPRIGHT", 20 * self.fontSizePercent, 0) + sixth:SetPoint("TOPLEFT", fifth, "TOPRIGHT", 20 * self.fontSizePercent, 0) + right:SetJustifyH("RIGHT") + local _,size = GameTooltipText:GetFont() + check:SetHeight(size * 1.5) + check:SetWidth(size * 1.5) + check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + check:SetAlpha(0) + if not button.clicked then + button:SetScript("OnMouseWheel", self:GetScript("OnMouseWheel")) + button:EnableMouseWheel(true) + button:Hide() + end + check:Show() + left:Hide() + right:Hide() + third:Hide() + fourth:Hide() + fifth:Hide() + sixth:Hide() + end +end +NewLine = wrap(NewLine, "NewLine") + +local function GetMaxLinesPerScreen(self) + if self.height_ then + local height = self:GetTop() and self:GetBottom() and self:GetTop() - self:GetBottom() or self:GetHeight() + if height == 0 then + height = self.height_ + end + height = height - 20 + return floor(height / (12*self.fontSizePercent+2)) + end + if self == tooltip then + return floor(50 / self.fontSizePercent) + else + return floor(30 / self.fontSizePercent) + end +end +GetMaxLinesPerScreen = wrap(GetMaxLinesPerScreen, "GetMaxLinesPerScreen") + +local overFrame = nil +local detachedTooltips = {} +local AcquireDetachedFrame, ReleaseDetachedFrame +local function AcquireFrame(self, registration, data, detachedData) + if not detachedData then + detachedData = data + end + if tooltip then + tooltip.data = data + tooltip.detachedData = detachedData + local fontSizePercent = tooltip.data and tooltip.data.fontSizePercent or 1 + local transparency = tooltip.data and tooltip.data.transparency or 0.75 + local r = tooltip.data and tooltip.data.r or 0 + local g = tooltip.data and tooltip.data.g or 0 + local b = tooltip.data and tooltip.data.b or 0 + tooltip:SetFontSizePercent(fontSizePercent) + tooltip:SetTransparency(transparency) + tooltip:SetColor(r, g, b) + tooltip:SetFrameStrata(registration.strata or "TOOLTIP") + else + tooltip = CreateFrame("Frame", "Tablet20Frame", UIParent) + self.tooltip = tooltip + tooltip.data = data + tooltip.detachedData = detachedData + tooltip:EnableMouse(true) + tooltip:EnableMouseWheel(true) + tooltip:SetFrameStrata(registration.strata or "TOOLTIP") + tooltip:SetFrameLevel(10) + local backdrop = new( + 'bgFile', "Interface\\Buttons\\WHITE8X8", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'tileSize', 16, + 'edgeSize', 16, + 'insets', new( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ) + ) + tooltip:SetBackdrop(backdrop) + del(backdrop.insets) + del(backdrop) + tooltip:SetBackdropColor(0, 0, 0, 1) + + tooltip.numLines = 0 + tooltip.owner = nil + tooltip.fontSizePercent = tooltip.data and tooltip.data.fontSizePercent or 1 + tooltip.maxLines = 0 + tooltip.buttons = {} + tooltip.checks = {} + tooltip.lefts = {} + tooltip.rights = {} + tooltip.thirds = {} + tooltip.fourths = {} + tooltip.fifths = {} + tooltip.sixths = {} + tooltip.transparency = tooltip.data and tooltip.data.transparency or 0.75 + tooltip:SetBackdropColor(0, 0, 0, tooltip.transparency) + tooltip:SetBackdropBorderColor(1, 1, 1, tooltip.transparency) + tooltip.scroll = 0 + + tooltip:SetScript("OnUpdate", function() + if not tooltip.updating and (not tooltip.enteredFrame or (overFrame and not MouseIsOver(overFrame))) then + tooltip.scroll = 0 + tooltip:Hide() + tooltip.registration.tooltip = nil + tooltip.registration = nil + overFrame = nil + end + end) + + tooltip:SetScript("OnEnter", function() + if tooltip.clickable then + tooltip.enteredFrame = true + overFrame = nil + end + end) + + tooltip:SetScript("OnLeave", function() + if not tooltip.updating then + tooltip.enteredFrame = false + end + end) + + tooltip:SetScript("OnMouseWheel", function() + tooltip.updating = true + tooltip:Scroll(arg1 < 0) + tooltip.updating = false + end) + + NewLine(tooltip) + + tooltip.scrollUp = tooltip:CreateFontString(nil, "ARTWORK") + tooltip.scrollUp:SetPoint("TOPLEFT", tooltip.buttons[1], "BOTTOMLEFT", 0, -2) + tooltip.scrollUp:SetPoint("RIGHT", tooltip, "RIGHT", 0, -10) + tooltip.scrollUp:SetFontObject(GameTooltipText) + tooltip.scrollUp:Hide() + local font,_,flags = tooltip.scrollUp:GetFont() + tooltip.scrollUp:SetFont(font, normalSize * tooltip.fontSizePercent, flags) + tooltip.scrollUp:SetJustifyH("CENTER") + tooltip.scrollUp:SetTextColor(1, 0.823529, 0) + tooltip.scrollUp:SetText(" ") + + tooltip.scrollDown = tooltip:CreateFontString(nil, "ARTWORK") + tooltip.scrollDown:SetPoint("TOPLEFT", tooltip.buttons[1], "BOTTOMLEFT", 0, -2) + tooltip.scrollDown:SetPoint("RIGHT", tooltip, "RIGHT", 0, -10) + tooltip.scrollDown:SetFontObject(GameTooltipText) + tooltip.scrollDown:Hide() + local font,_,flags = tooltip.scrollUp:GetFont() + tooltip.scrollDown:SetFont(font, normalSize * tooltip.fontSizePercent, flags) + tooltip.scrollDown:SetJustifyH("CENTER") + tooltip.scrollDown:SetTextColor(1, 0.823529, 0) + tooltip.scrollDown:SetText(" ") + + function tooltip:SetOwner(o) + self:Hide(o) + self.owner = o + end + tooltip.SetOwner = wrap(tooltip.SetOwner, "tooltip:SetOwner") + + function tooltip:IsOwned(o) + return self.owner == o + end + tooltip.IsOwned = wrap(tooltip.IsOwned, "tooltip:IsOwned") + + function tooltip:ClearLines(hide) + CleanCategoryPool(self) + for i = 1, self.numLines do + local button = self.buttons[i] + local check = self.checks[i] + if not button.clicked or hide then + button:Hide() + end + check.shown = false + check:SetAlpha(0) + end + self.numLines = 0 + end + tooltip.ClearLines = wrap(tooltip.ClearLines, "tooltip:ClearLines") + + function tooltip:NumLines() + return self.numLines + end + + local lastWidth + local old_tooltip_Hide = tooltip.Hide + tooltip.__Hide = old_tooltip_Hide + function tooltip:Hide(newOwner) + if self == tooltip or newOwner == nil then + old_tooltip_Hide(self) + end + self:ClearLines(true) + self.owner = nil + self.lastWidth = nil + self.tmpHidden = nil + end + tooltip.Hide = wrap(tooltip.Hide, "tooltip:Hide") + + local old_tooltip_Show = tooltip.Show + tooltip.__Show = old_tooltip_Show + function tooltip:Show(tabletData) + if self.owner == nil or self.notInUse then + return + end + if not self.tmpHidden then + old_tooltip_Show(self) + end + + testString = self.lefts[1] + + local maxWidth = tabletData and tabletData.width or self:GetWidth() - 20 + local hasWrap = false + local screenWidth = GetScreenWidth() + local scrollMax = self.numLines + local maxLinesPerScreen = GetMaxLinesPerScreen(self) + if scrollMax > maxLinesPerScreen + self.scroll then + scrollMax = maxLinesPerScreen + self.scroll + end + local numColumns + + local height = 20 + if scrollMax ~= self.numLines then + self.scrollDown:SetWidth(maxWidth) + height = height + self.scrollDown:GetHeight() + 2 + end + if self.scroll ~= 0 then + self.scrollUp:SetWidth(maxWidth) + height = height + self.scrollUp:GetHeight() + 2 + end + self:SetWidth(maxWidth + 20) + + local tmp = self.scroll + 1 + if tmp ~= 1 then + tmp = tmp + 1 + end + for i = 1, self.numLines do + if i < tmp or i > scrollMax or (i == scrollMax and i ~= self.numLines) then + self.buttons[i]:ClearAllPoints() + self.buttons[i]:Hide() + else + local button = self.buttons[i] + local left = self.lefts[i] + local right = self.rights[i] + local check = self.checks[i] + button:SetWidth(maxWidth) + button:SetHeight(math.max(left:GetHeight(), right:GetHeight())) + height = height + button:GetHeight() + 2 + if i == self.scroll + 1 then + button:SetPoint("TOPLEFT", self, "TOPLEFT", 10, -10) + else + button:SetPoint("TOPLEFT", self.buttons[i - 1], "BOTTOMLEFT", 0, -2) + end + if button.clicked then + check:SetPoint("TOPLEFT", button, "TOPLEFT", button.indentation * self.fontSizePercent + (check.width - check:GetWidth()) / 2 + 1, -1) + else + check:SetPoint("TOPLEFT", button, "TOPLEFT", button.indentation * self.fontSizePercent + (check.width - check:GetWidth()) / 2, 0) + end + button:Show() + end + end + if self.scroll ~= 0 then + self.scrollUp:SetPoint("TOPLEFT", self, "TOPLEFT", 10, -10) + self.buttons[self.scroll + 2]:SetPoint("TOPLEFT", self.scrollUp, "BOTTOMLEFT", 0, -2) + self.scrollUp:SetText(SCROLL_UP .. " (" .. self.scroll + 2 .. " / " .. self.numLines .. ")") + self.scrollUp:Show() + else + self.scrollUp:Hide() + end + if scrollMax ~= self.numLines and self.buttons[scrollMax - 1] then + self.scrollDown:SetPoint("TOPLEFT", self.buttons[scrollMax - 1], "BOTTOMLEFT", 0, -2) + self.scrollDown:SetText(SCROLL_DOWN .. " (" .. scrollMax - 1 .. " / " .. self.numLines .. ")") + self.scrollDown:Show() + else + self.scrollDown:Hide() + end + self:SetHeight(height) + end + tooltip.Show = wrap(tooltip.Show, "tooltip:Show") + + local lastMouseDown + local function button_OnClick() + if this.self:HasScript("OnClick") and type(this.self:GetScript("OnClick")) == "function" then + this.self:GetScript("OnClick")() + end + if arg1 == "RightButton" then + if this.self:HasScript("OnClick") and type(this.self:GetScript("OnClick")) == "function" then + this.self:GetScript("OnClick")() + end + elseif arg1 == "LeftButton" then + if this.self.preventClick == nil or GetTime() > this.self.preventClick and GetTime() < lastMouseDown + 0.5 then + this.self.preventClick = nil + this.self.updating = true + this.self.preventRefresh = true + this.func(unpack(this)) + if this.self and this.self.registration then + this.self.preventRefresh = false + this.self:children() + this.self.updating = false + end + end + end + end + local function button_OnMouseUp() + if this.self:HasScript("OnMouseUp") and type(this.self:GetScript("OnMouseUp")) == "function" then + this.self:GetScript("OnMouseUp")() + end + if arg1 ~= "RightButton" then + if this.clicked then + local a,b,c,d,e = this.check:GetPoint(1) + this.check:SetPoint(a,b,c,d-1,e+1) + this.clicked = false + end + end + end + local function button_OnMouseDown() + if this.self:HasScript("OnMouseDown") and type(this.self:GetScript("OnMouseDown")) == "function" then + this.self:GetScript("OnMouseDown")() + end + lastMouseDown = GetTime() + if arg1 ~= "RightButton" then + local a,b,c,d,e = this.check:GetPoint(1) + this.check:SetPoint(a,b,c,d+1,e-1) + this.clicked = true + end + end + function tooltip:AddLine(info) + local category = info.category.superCategory + local maxWidth = category.tabletData.width + local text = info.blank and "\n" or info.text + local id = info.id + local func = info.func + local checked = info.checked + local isRadio = info.isRadio + local checkTexture = info.checkTexture + local fontSizePercent = self.fontSizePercent + if not info.font then + info.font = GameTooltipText + end + if not info.size then + _,info.size = info.font:GetFont() + end + local catStart = false + local columns = category and category.columns or 1 + local x1, x2, x3, x4, x5, x6 + if category then + x1, x2, x3, x4, x5, x6 = category.x1, category.x2, category.x3, category.x4, category.x5, category.x6 + else + x1, x2, x3, x4, x5, x6 = 0, 0, 0, 0, 0, 0 + end + if info.isTitle then + justAddedTitle = true + end + + self.numLines = self.numLines + 1 + NewLine(self) + self.lefts[self.numLines]:Show() + self.buttons[self.numLines]:Show() + num = self.numLines + + local button = self.buttons[num] + button.indentation = info.indentation + local left = self.lefts[num] +-- testString = left + + local right = self.rights[num] + local third = self.thirds[num] + local fourth = self.fourths[num] + local fifth = self.fifths[num] + local sixth = self.sixths[num] + local check = self.checks[num] + do -- if columns >= 1 then + left:SetWidth(0) + left:SetFontObject(info.font) + local font,_,flags = left:GetFont() + left:SetFont(font, info.size * fontSizePercent, flags) + left:SetText(text) + left:SetJustifyH(info.justify) + left:Show() + + if info.textR and info.textG and info.textB then + left:SetTextColor(info.textR, info.textG, info.textB) + else + left:SetTextColor(1, 0.823529, 0) + end + if columns < 2 then + right:SetText(nil) + right:Hide() + right:SetPoint("TOPLEFT", left, "TOPRIGHT", 40 * fontSizePercent, 0) + right:SetPoint("TOPRIGHT", button, "TOPRIGHT", -5, 0) + third:SetText(nil) + third:Hide() + fourth:SetText(nil) + fourth:Hide() + fifth:SetText(nil) + fifth:Hide() + sixth:SetText(nil) + sixth:Hide() + else + right:SetFontObject(info.font2) + right:SetText(info.text2) + right:Show() + if info.text2R and info.text2G and info.text2B then + right:SetTextColor(info.text2R, info.text2G, info.text2B) + else + right:SetTextColor(1, 0.823529, 0) + end + local a,_,b = right:GetFont() + right:SetFont(a, info.size2 * fontSizePercent, b) + right:SetJustifyH(info.justify2) + if columns < 3 then + right:SetPoint("TOPLEFT", left, "TOPRIGHT", 40 * fontSizePercent, 0) + right:SetPoint("TOPRIGHT", button, "TOPRIGHT", -5, 0) + third:SetText(nil) + third:Hide() + fourth:SetText(nil) + fourth:Hide() + fifth:SetText(nil) + fifth:Hide() + sixth:SetText(nil) + sixth:Hide() + else + third:SetFontObject(info.font3) + third:SetText(info.text3) + third:Show() + if info.text3R and info.text3G and info.text3B then + third:SetTextColor(info.text3R, info.text3G, info.text3B) + else + third:SetTextColor(1, 0.823529, 0) + end + local a,_,b = third:GetFont() + third:SetFont(a, info.size3 * fontSizePercent, b) + right:ClearAllPoints() + right:SetPoint("TOPLEFT", left, "TOPRIGHT", (20 - info.indentation) * fontSizePercent, 0) + third:SetJustifyH(info.justify3) + if columns < 4 then + fourth:SetText(nil) + fourth:Hide() + fifth:SetText(nil) + fifth:Hide() + sixth:SetText(nil) + sixth:Hide() + else + fourth:SetFontObject(info.font4) + fourth:SetText(info.text4) + fourth:Show() + if info.text4R and info.text4G and info.text4B then + fourth:SetTextColor(info.text4R, info.text4G, info.text4B) + else + fourth:SetTextColor(1, 0.823529, 0) + end + local a,_,b = fourth:GetFont() + fourth:SetFont(a, info.size4 * fontSizePercent, b) + fourth:SetJustifyH(info.justify4) + if columns < 5 then + fifth:SetText(nil) + fifth:Hide() + sixth:SetText(nil) + sixth:Hide() + else + fifth:SetFontObject(info.font5) + fifth:SetText(info.text5) + fifth:Show() + if info.text5R and info.text5G and info.text5B then + fifth:SetTextColor(info.text5R, info.text5G, info.text5B) + else + fifth:SetTextColor(1, 0.823529, 0) + end + local a,_,b = fourth:GetFont() + fifth:SetFont(a, info.size5 * fontSizePercent, b) + fifth:SetJustifyH(info.justify5) + if columns < 6 then + sixth:SetText(nil) + sixth:Hide() + else + sixth:SetFontObject(info.font6) + sixth:SetText(info.text6) + sixth:Show() + if info.text5R and info.text6G and info.text6B then + sixth:SetTextColor(info.text6R, info.text6G, info.text6B) + else + sixth:SetTextColor(1, 0.823529, 0) + end + local a,_,b = fourth:GetFont() + sixth:SetFont(a, info.size6 * fontSizePercent, b) + sixth:SetJustifyH(info.justify6) + end + end + end + end + end + end + + check:SetWidth(info.size * fontSizePercent) + check:SetHeight(info.size * fontSizePercent) + check.width = info.size * fontSizePercent + if info.hasCheck then + check.shown = true + check:Show() + if isRadio then + check:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") + if info.checked then + check:SetAlpha(1) + check:SetTexCoord(0.25, 0.5, 0, 1) + else + check:SetAlpha(self.transparency) + check:SetTexCoord(0, 0.25, 0, 1) + end + else + if info.checkIcon then + check:SetTexture(info.checkIcon) + if string.sub(info.checkIcon, 1, 16) == "Interface\\Icons\\" then + check:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + check:SetTexCoord(0, 1, 0, 1) + end + else + check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + check:SetWidth(info.size * fontSizePercent * 1.5) + check:SetHeight(info.size * fontSizePercent * 1.5) + check.width = info.size * fontSizePercent * 1.2 + check:SetTexCoord(0, 1, 0, 1) + end + check:SetAlpha(info.checked and 1 or 0) + end + left:SetPoint("TOPLEFT", check, "TOPLEFT", check.width, 0) + else + left:SetPoint("TOPLEFT", check, "TOPLEFT") + end + if columns == 1 then + left:SetWidth(maxWidth) + elseif columns == 2 then + left:SetWidth(0) + right:SetWidth(0) + if info.wrap then + left:SetWidth(maxWidth - right:GetWidth() - 40 * fontSizePercent) + elseif info.wrap2 then + right:SetWidth(maxWidth - left:GetWidth() - 40 * fontSizePercent) + end + right:ClearAllPoints() + right:SetPoint("TOPRIGHT", button, "TOPRIGHT", 0, 0) + if not info.text2 then + left:SetJustifyH(info.justify or "LEFT") + end + elseif columns == 3 then + left:SetWidth(x1 - info.checkWidth) + right:SetWidth(x2) + third:SetWidth(x3) + right:ClearAllPoints() + local num = (category.tabletData.width - x1 - x2 - x3) / 2 + right:SetPoint("TOPLEFT", left, "TOPRIGHT", num - info.indentation * fontSizePercent, 0) + third:SetPoint("TOPLEFT", right, "TOPRIGHT", num, 0) + elseif columns == 4 then + left:SetWidth(x1 - info.checkWidth) + right:SetWidth(x2) + third:SetWidth(x3) + fourth:SetWidth(x4) + local num = (category.tabletData.width - x1 - x2 - x3 - x4) / 3 + right:SetPoint("TOPLEFT", left, "TOPRIGHT", num - info.indentation * fontSizePercent, 0) + third:SetPoint("TOPLEFT", right, "TOPRIGHT", num, 0) + fourth:SetPoint("TOPLEFT", third, "TOPRIGHT", num, 0) + elseif columns == 5 then + left:SetWidth(x1 - info.checkWidth) + right:SetWidth(x2) + third:SetWidth(x3) + fourth:SetWidth(x4) + fifth:SetWidth(x5) + local num = (category.tabletData.width - x1 - x2 - x3 - x4 - x5) / 4 + right:SetPoint("TOPLEFT", left, "TOPRIGHT", num - info.indentation * fontSizePercent, 0) + third:SetPoint("TOPLEFT", right, "TOPRIGHT", num, 0) + fourth:SetPoint("TOPLEFT", third, "TOPRIGHT", num, 0) + fifth:SetPoint("TOPLEFT", fourth, "TOPRIGHT", num, 0) + elseif columns == 6 then + left:SetWidth(x1 - info.checkWidth) + right:SetWidth(x2) + third:SetWidth(x3) + fourth:SetWidth(x4) + fifth:SetWidth(x5) + sixth:SetWidth(x6) + local num = (category.tabletData.width - x1 - x2 - x3 - x4 - x5 - x6) / 5 + right:SetPoint("TOPLEFT", left, "TOPRIGHT", num - info.indentation * fontSizePercent, 0) + third:SetPoint("TOPLEFT", right, "TOPRIGHT", num, 0) + fourth:SetPoint("TOPLEFT", third, "TOPRIGHT", num, 0) + fifth:SetPoint("TOPLEFT", fourth, "TOPRIGHT", num, 0) + sixth:SetPoint("TOPLEFT", fifth, "TOPRIGHT", num, 0) + end + if not self.locked or IsAltKeyDown() then + local func = info.func + if func then + if type(func) == "string" then + if type(info.arg1) ~= "table" then + Tablet:error("Cannot call method " .. info.func .. " on a non-table") + end + func = info.arg1[func] + if type(func) ~= "function" then + Tablet:error("Method " .. info.func .. " nonexistant") + end + else + if type(func) ~= "function" then + Tablet:error("func must be a function or method") + end + end + button.func = func + local i = 1 + while true do + if button[i] ~= nil then + button[i] = nil + else + break + end + i = i + 1 + end + i = 1 + while true do + local k = 'arg' .. i + local v = info[k] + if v == nil then + break + end + button[i] = v + i = i + 1 + end + button.self = self + button:SetScript("OnMouseUp", button_OnMouseUp) + button:SetScript("OnMouseDown", button_OnMouseDown) + button:SetScript("OnClick", button_OnClick) + if button.clicked then + button:SetButtonState("PUSHED") + end + button:EnableMouse(true) + else + button:SetScript("OnMouseDown", nil) + button:SetScript("OnMouseUp", nil) + button:SetScript("OnClick", nil) + button:EnableMouse(false) + end + else + button:SetScript("OnMouseDown", nil) + button:SetScript("OnMouseUp", nil) + button:SetScript("OnClick", nil) + button:EnableMouse(false) + end + end + tooltip.AddLine = wrap(tooltip.AddLine, "tooltip:AddLine") + + function tooltip:SetFontSizePercent(percent) + local data, detachedData = self.data, self.detachedData + if detachedData and detachedData.detached then + data = detachedData + end + local lastSize = self.fontSizePercent + percent = tonumber(percent) or 1 + if percent < 0.25 then + percent = 0.25 + elseif percent > 4 then + percent = 4 + end + self.fontSizePercent = percent + if data then + data.fontSizePercent = percent + end + self.scrollUp:SetFont(font, normalSize * self.fontSizePercent, flags) + self.scrollDown:SetFont(font, normalSize * self.fontSizePercent, flags) + local ratio = self.fontSizePercent / lastSize + for i = 1, self.numLines do + local left = self.lefts[i] + local right = self.rights[i] + local third = self.thirds[i] + local fourth = self.fourths[i] + local fifth = self.fifths[i] + local sixth = self.sixths[i] + local check = self.checks[i] + local font, size, flags = left:GetFont() + left:SetFont(font, size * ratio, flags) + font, size, flags = right:GetFont() + right:SetFont(font, size * ratio, flags) + font, size, flags = third:GetFont() + third:SetFont(font, size * ratio, flags) + font, size, flags = fourth:GetFont() + fourth:SetFont(font, size * ratio, flags) + font, size, flags = fifth:GetFont() + fifth:SetFont(font, size * ratio, flags) + font, size, flags = sixth:GetFont() + sixth:SetFont(font, size * ratio, flags) + check.width = check.width * ratio + check:SetWidth(check:GetWidth() * ratio) + check:SetHeight(check:GetHeight() * ratio) + end + self:SetWidth((self:GetWidth() - 51) * ratio + 51) + self:SetHeight((self:GetHeight() - 51) * ratio + 51) + if self:IsShown() and self.children then + self:children() + self:Show() + end + end + tooltip.SetFontSizePercent = wrap(tooltip.SetFontSizePercent, "tooltip:SetFontSizePercent") + + function tooltip:GetFontSizePercent() + return self.fontSizePercent + end + + function tooltip:SetTransparency(alpha) + local data, detachedData = self.data, self.detachedData + if detachedData and detachedData.detached then + data = detachedData + end + self.transparency = alpha + if data then + data.transparency = alpha ~= 0.75 and alpha or nil + end + self:SetBackdropColor(self.r or 0, self.g or 0, self.b or 0, alpha) + self:SetBackdropBorderColor(1, 1, 1, alpha) + end + tooltip.SetTransparency = wrap(tooltip.SetTransparency, "tooltip:SetTransparency") + + function tooltip:GetTransparency() + return self.transparency + end + + function tooltip:SetColor(r, g, b) + local data, detachedData = self.data, self.detachedData + if detachedData and detachedData.detached then + data = detachedData + end + self.r = r + self.g = g + self.b = b + if data then + data.r = r ~= 0 and r or nil + data.g = g ~= 0 and g or nil + data.b = b ~= 0 and b or nil + end + self:SetBackdropColor(r or 0, g or 0, b or 0, self.transparency) + self:SetBackdropBorderColor(1, 1, 1, self.transparency) + end + tooltip.SetColor = wrap(tooltip.SetColor, "tooltip:SetColor") + + function tooltip:GetColor() + return self.r, self.g, self.b + end + + function tooltip:Scroll(down) + local maxLinesPerScreen = GetMaxLinesPerScreen(self) + if down then + if IsShiftKeyDown() then + self.scroll = self.numLines - maxLinesPerScreen + else + self.scroll = self.scroll + 3 + end + else + if IsShiftKeyDown() then + self.scroll = 0 + else + self.scroll = self.scroll - 3 + end + end + if self.scroll > self.numLines - maxLinesPerScreen then + self.scroll = self.numLines - maxLinesPerScreen + end + if self.scroll < 0 then + self.scroll = 0 + end + if self:IsShown() then + self:Show() + end + end + tooltip.Scroll = wrap(tooltip.Scroll, "tooltip:Scroll") + + function tooltip.Detach(tooltip) + local owner = tooltip.owner + tooltip:Hide() + if not tooltip.detachedData then + self:error("You cannot detach if detachedData is not present") + end + tooltip.detachedData.detached = true + local detached = AcquireDetachedFrame(self, tooltip.registration, tooltip.data, tooltip.detachedData) + + detached.menu, tooltip.menu = tooltip.menu, nil + detached.children = tooltip.children + tooltip.children = nil + detached:SetOwner(owner) + detached:children() + detached:Show() + end + tooltip.Detach = wrap(tooltip.Detach, "tooltip:Detach") + + end + + tooltip.registration = registration + registration.tooltip = tooltip + return tooltip +end +AcquireFrame = wrap(AcquireFrame, "AcquireFrame") + +function ReleaseDetachedFrame(self, data, detachedData) + if not detachedData then + detachedData = data + end + for _, detached in ipairs(detachedTooltips) do + if detached.detachedData == detachedData then + detached.notInUse = true + detached:Hide() + detached.registration.tooltip = nil + detached.registration = nil + detached.detachedData = nil + end + end +end +ReleaseDetachedFrame = wrap(ReleaseDetachedFrame, "ReleaseDetachedFrame") + +local StartCheckingAlt, StopCheckingAlt +do + local frame + function StartCheckingAlt(func) + if not frame then + frame = CreateFrame("Frame") + frame:SetScript("OnEvent", function(this, _, modifier) + if modifier == "ALT" then + this.func() + end + end) + end + frame:RegisterEvent("MODIFIER_STATE_CHANGED") + frame.func = func + end + function StopCheckingAlt() + if frame then + frame:UnregisterEvent("MODIFIER_STATE_CHANGED") + end + end +end + +local fake_ipairs +do + local function iter(tmp, i) + i = i + 1 + local x = tmp[i] + tmp[i] = nil + if x then + return i, x + end + end + + local tmp = {} + function fake_ipairs(...) + for i = 1, select('#', ...) do + tmp[i] = select(i, ...) + end + return iter, tmp, 0 + end +end + +function AcquireDetachedFrame(self, registration, data, detachedData) + if not detachedData then + detachedData = data + end + for _, detached in ipairs(detachedTooltips) do + if detached.notInUse then + detached.data = data + detached.detachedData = detachedData + detached.notInUse = nil + local fontSizePercent = detachedData.fontSizePercent or 1 + local transparency = detachedData.transparency or 0.75 + local r = detachedData.r or 0 + local g = detachedData.g or 0 + local b = detachedData.b or 0 + detached:SetFontSizePercent(fontSizePercent) + detached:SetTransparency(transparency) + detached:SetColor(r, g, b) + detached:ClearAllPoints() + detached:SetWidth(0) + detached:SetHeight(0) + if not registration.strata then + detached:SetFrameStrata("BACKGROUND") + end + if not registration.frameLevel then + detached:SetFrameLevel(10) + for _,frame in fake_ipairs(detached:GetChildren()) do + frame:SetFrameLevel(12) + end + end + detached:SetParent(registration.parent or UIParent) + if registration.strata then + detached:SetFrameStrata(registration.strata) + end + if registration.frameLevel then + detached:SetFrameLevel(registration.frameLevel) + for _,frame in fake_ipairs(detached:GetChildren()) do + frame:SetFrameLevel(registration.frameLevel+2) + end + end + detached.height_ = nil + if registration.positionFunc then + registration.positionFunc(detached) + local height = detached:GetHeight() + if height > 0 then + detached.height_ = height + else + local top, bottom + for i = 1, detached:GetNumPoints() do + local a,b,c,d,e = detached:GetPoint(i) + + if a:find("^TOP") then + if c:find("^TOP") then + top = b:GetTop() + elseif c:find("^BOTTOM") then + top = b:GetBottom() + else + _,top = b:GetCenter() + end + if top then + top = top + e + end + elseif a:find("^BOTTOM") then + if c:find("^TOP") then + bottom = b:GetTop() + elseif c:find("^BOTTOM") then + bottom = b:GetBottom() + else + _,bottom = b:GetCenter() + end + if bottom then + bottom = bottom + e + end + end + end + if top and bottom then + detached.height_ = top - bottom + end + end + else + detached:SetPoint(detachedData.anchor or "CENTER", UIParent, detachedData.anchor or "CENTER", detachedData.offsetx or 0, detachedData.offsety or 0) + end + detached.registration = registration + registration.tooltip = detached + if registration.movable == false then + detached:RegisterForDrag() + else + detached:RegisterForDrag("LeftButton") + end + return detached + end + end + + if not Dewdrop and AceLibrary:HasInstance("Dewdrop-2.0") then + Dewdrop = AceLibrary("Dewdrop-2.0") + end + StartCheckingAlt(function() + for _, detached in ipairs(detachedTooltips) do + if detached:IsShown() and detached.locked then + detached:EnableMouse(IsAltKeyDown()) + detached:children() + if detached.moving then + local a1 = arg1 + arg1 = "LeftButton" + if type(detached:GetScript("OnMouseUp")) == "function" then + detached:GetScript("OnMouseUp")(detached, arg1) + end + arg1 = a1 + end + end + end + end) + if not tooltip then + AcquireFrame(self, {}) + end + local detached = CreateFrame("Frame", "Tablet20DetachedFrame" .. (#detachedTooltips + 1), UIParent) + table.insert(detachedTooltips, detached) + detached.notInUse = true + detached:EnableMouse(not data.locked) + detached:EnableMouseWheel(true) + detached:SetMovable(true) + detached:SetPoint(data.anchor or "CENTER", UIParent, data.anchor or "CENTER", data.offsetx or 0, data.offsety or 0) + + detached.numLines = 0 + detached.owner = nil + detached.fontSizePercent = 1 + detached.maxLines = 0 + detached.buttons = {} + detached.checks = {} + detached.lefts = {} + detached.rights = {} + detached.thirds = {} + detached.fourths = {} + detached.fifths = {} + detached.sixths = {} + detached.transparency = 0.75 + detached.r = 0 + detached.g = 0 + detached.b = 0 + detached:SetFrameStrata(registration.strata or "BACKGROUND") + detached:SetBackdrop(tmp.a( + 'bgFile', "Interface\\Buttons\\WHITE8X8", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'tileSize', 16, + 'edgeSize', 16, + 'insets', tmp.b( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ) + )) + detached.locked = detachedData.locked + detached.scroll = 0 + detached:EnableMouse(not detached.locked) + + local width = GetScreenWidth() + local height = GetScreenHeight() + if registration.movable == false then + detached:RegisterForDrag() + else + detached:RegisterForDrag("LeftButton") + end + detached:SetScript("OnDragStart", function() + detached:StartMoving() + detached.moving = true + end) + + detached:SetScript("OnDragStop", function() + detached:StopMovingOrSizing() + detached.moving = nil + local anchor + local offsetx + local offsety + if detached:GetTop() + detached:GetBottom() < height then + anchor = "BOTTOM" + offsety = detached:GetBottom() + if offsety < 0 then + offsety = 0 + end + if offsety < MainMenuBar:GetTop() and MainMenuBar:IsVisible() then + offsety = MainMenuBar:GetTop() + end + local top = 0 + if FuBar then + for i = 1, FuBar:GetNumPanels() do + local panel = FuBar:GetPanel(i) + if panel:GetAttachPoint() == "BOTTOM" then + if panel.frame:GetTop() > top then + top = panel.frame:GetTop() + break + end + end + end + end + if offsety < top then + offsety = top + end + else + anchor = "TOP" + offsety = detached:GetTop() - height + if offsety > 0 then + offsety = 0 + end + local bottom = GetScreenHeight() + if FuBar then + for i = 1, FuBar:GetNumPanels() do + local panel = FuBar:GetPanel(i) + if panel:GetAttachPoint() == "TOP" then + if panel.frame:GetBottom() < bottom then + bottom = panel.frame:GetBottom() + break + end + end + end + end + bottom = bottom - GetScreenHeight() + if offsety > bottom then + offsety = bottom + end + end + if detached:GetLeft() + detached:GetRight() < width * 2 / 3 then + anchor = anchor .. "LEFT" + offsetx = detached:GetLeft() + if offsetx < 0 then + offsetx = 0 + end + elseif detached:GetLeft() + detached:GetRight() < width * 4 / 3 then + if anchor == "" then + anchor = "CENTER" + end + offsetx = (detached:GetLeft() + detached:GetRight() - GetScreenWidth()) / 2 + else + anchor = anchor .. "RIGHT" + offsetx = detached:GetRight() - width + if offsetx > 0 then + offsetx = 0 + end + end + detached:ClearAllPoints() + detached:SetPoint(anchor, UIParent, anchor, offsetx, offsety) + local t = detached.detachedData + if t.anchor ~= anchor or math.abs(t.offsetx - offsetx) > 8 or math.abs(t.offsety - offsety) > 8 then + detached.preventClick = GetTime() + 0.05 + end + t.anchor = anchor + t.offsetx = offsetx + t.offsety = offsety + detached:Show() + end) + + if Dewdrop then + Dewdrop:Register(detached, + 'children', function(level, value) + if not detached.registration then + return + end + if detached.menu then + detached.menu(level, value) + if level == 1 then + Dewdrop:AddLine() + end + end + if level == 1 then + if not detached.registration.cantAttach then + Dewdrop:AddLine( + 'text', DETACH, + 'tooltipTitle', DETACH, + 'tooltipText', DETACH_DESC, + 'checked', true, + 'arg1', detached, + 'func', "Attach", + 'closeWhenClicked', true + ) + end + Dewdrop:AddLine( + 'text', LOCK, + 'tooltipTitle', LOCK, + 'tooltipText', LOCK_DESC, + 'checked', detached:IsLocked(), + 'arg1', detached, + 'func', "Lock", + 'closeWhenClicked', not detached:IsLocked() + ) + Dewdrop:AddLine( + 'text', COLOR, + 'tooltipTitle', COLOR, + 'tooltipText', COLOR_DESC, + 'hasColorSwatch', true, + 'r', detached.r, + 'g', detached.g, + 'b', detached.b, + 'hasOpacity', true, + 'opacity', detached.transparency, + 'colorFunc', function(r, g, b, a) + detached:SetColor(r, g, b) + detached:SetTransparency(a) + end + ) + Dewdrop:AddLine( + 'text', SIZE, + 'tooltipTitle', SIZE, + 'tooltipText', SIZE_DESC, + 'hasArrow', true, + 'hasSlider', true, + 'sliderFunc', function(value) + detached:SetFontSizePercent(value) + end, + 'sliderMax', 2, + 'sliderMin', 0.5, + 'sliderStep', 0.05, + 'sliderIsPercent', true, + 'sliderValue', detached:GetFontSizePercent() + ) + Dewdrop:AddLine( + 'text', CLOSE_MENU, + 'tooltipTitle', CLOSE_MENU, + 'tooltipText', CLOSE_MENU_DESC, + 'func', function() + Dewdrop:Close() + end + ) + end + end, + 'point', function() + local x, y = detached:GetCenter() + if x < GetScreenWidth() / 2 then + if y < GetScreenHeight() / 2 then + return "BOTTOMLEFT", "BOTTOMRIGHT" + else + return "TOPLEFT", "TOPRIGHT" + end + else + if y < GetScreenHeight() / 2 then + return "BOTTOMRIGHT", "BOTTOMLEFT" + else + return "TOPRIGHT", "TOPLEFT" + end + end + end + ) + end + + NewLine(detached) + + detached.scrollUp = detached:CreateFontString(nil, "ARTWORK") + detached.scrollUp:SetPoint("TOPLEFT", detached.buttons[1], "BOTTOMLEFT", 0, -2) + detached.scrollUp:SetPoint("RIGHT", detached, "RIGHT", 0, -10) + detached.scrollUp:SetFontObject(GameTooltipText) + detached.scrollUp:Hide() + local font,_,flags = detached.scrollUp:GetFont() + detached.scrollUp:SetFont(font, normalSize * detached.fontSizePercent, flags) + detached.scrollUp:SetJustifyH("CENTER") + detached.scrollUp:SetTextColor(1, 0.823529, 0) + detached.scrollUp:SetText(" ") + + detached.scrollDown = detached:CreateFontString(nil, "ARTWORK") + detached.scrollDown:SetPoint("TOPLEFT", detached.buttons[1], "BOTTOMLEFT", 0, -2) + detached.scrollDown:SetPoint("RIGHT", detached, "RIGHT", 0, -10) + detached.scrollDown:SetFontObject(GameTooltipText) + detached.scrollDown:Hide() + local font,_,flags = detached.scrollUp:GetFont() + detached.scrollDown:SetFont(font, normalSize * detached.fontSizePercent, flags) + detached.scrollDown:SetJustifyH("CENTER") + detached.scrollDown:SetTextColor(1, 0.823529, 0) + detached.scrollDown:SetText(" ") + + detached:SetScript("OnMouseWheel", function() + detached:Scroll(arg1 < 0) + end) + + detached.SetTransparency = tooltip.SetTransparency + detached.GetTransparency = tooltip.GetTransparency + detached.SetColor = tooltip.SetColor + detached.GetColor = tooltip.GetColor + detached.SetFontSizePercent = tooltip.SetFontSizePercent + detached.GetFontSizePercent = tooltip.GetFontSizePercent + detached.SetOwner = tooltip.SetOwner + detached.IsOwned = tooltip.IsOwned + detached.ClearLines = tooltip.ClearLines + detached.NumLines = tooltip.NumLines + detached.__Hide = detached.Hide + detached.__Show = detached.Show + detached.Hide = tooltip.Hide + detached.Show = tooltip.Show + local old_IsShown = detached.IsShown + function detached:IsShown() + if self.tmpHidden then + return true + else + return old_IsShown(self) + end + end + detached.AddLine = tooltip.AddLine + detached.Scroll = tooltip.Scroll + function detached:IsLocked() + return self.locked + end + function detached:Lock() + self:EnableMouse(self.locked) + self.locked = not self.locked + self.detachedData.locked = self.locked or nil + self:children() + end + + function detached.Attach(detached) + if not detached then + self:error("Detached tooltip not given.") + end + if not detached.AddLine then + self:error("detached argument not a Tooltip.") + end + if not detached.owner then + self:error("Detached tooltip has no owner.") + end + if detached.notInUse then + self:error("Detached tooltip not in use.") + end + detached.menu = nil + detached.detachedData.detached = nil + detached:SetOwner(nil) + detached.notInUse = true + end + + return AcquireDetachedFrame(self, registration, data, detachedData) +end +AcquireDetachedFrame = wrap(AcquireDetachedFrame, "AcquireDetachedFrame") + +function Tablet:Close(parent) + if not parent then + if tooltip and tooltip:IsShown() then + tooltip:Hide() + tooltip.registration.tooltip = nil + tooltip.registration = nil + tooltip.enteredFrame = false + end + return + else + self:argCheck(parent, 2, "table", "string") + end + local info = self.registry[parent] + if not info then + self:error("You cannot close a tablet with an unregistered parent frame.") + end + local data = info.data + local detachedData = info.detachedData + if detachedData and detachedData.detached then + ReleaseDetachedFrame(self, data, detachedData) + elseif tooltip.data == data then + tooltip:Hide() + tooltip.registration.tooltip = nil + tooltip.registration = nil + end + tooltip.enteredFrame = false +end +Tablet.Close = wrap(Tablet.Close, "Tablet:Close") + +local currentFrame +local currentTabletData + +function Tablet:Open(fakeParent, parent) + self:argCheck(fakeParent, 2, "table", "string") + self:argCheck(parent, 3, "nil", "table", "string") + if not parent then + parent = fakeParent + end + local info = self.registry[parent] + if not info then + self:error("You cannot open a tablet with an unregistered parent frame.") + end + local detachedData = info.detachedData + if detachedData then + for _, detached in ipairs(detachedTooltips) do + if not detached.notInUse and detached.detachedData == detachedData then + return + end + end + end +-- self:Close(fakeParent) + local data = info.data + local children = info.children + if not children then + return + end + local frame = AcquireFrame(self, info, data, detachedData) + frame.clickable = info.clickable + frame.menu = info.menu + local children = info.children + function frame:children() + if not self.preventRefresh and self:GetParent():IsVisible() then + currentFrame = self + currentTabletData = TabletData:new(self) +-- if info.width then +-- currentTabletData.width = info.width +-- end + self:ClearLines() + if children then + children() + end + currentTabletData:Display(currentFrame) + self:Show(currentTabletData) + currentTabletData:del() + currentTabletData = nil + currentFrame = nil + end + end + frame:SetOwner(fakeParent) + frame:children() + local point = info.point + local relativePoint = info.relativePoint + if type(point) == "function" then + local b + point, b = point(fakeParent) + if b then + relativePoint = b + end + end + if type(relativePoint) == "function" then + relativePoint = relativePoint(fakeParent) + end + if not point then + point = "CENTER" + end + if not relativePoint then + relativePoint = point + end + frame:ClearAllPoints() + if type(parent) ~= "string" then + frame:SetPoint(point, fakeParent, relativePoint) + end + local offsetx = 0 + local offsety = 0 + if frame:GetBottom() and frame:GetLeft() then + if frame:GetRight() > GetScreenWidth() then + offsetx = frame:GetRight() - GetScreenWidth() + elseif frame:GetLeft() < 0 then + offsetx = -frame:GetLeft() + end + local ratio = GetScreenWidth() / GetScreenHeight() + if ratio >= 2.4 and frame:GetRight() > GetScreenWidth() / 2 and frame:GetLeft() < GetScreenWidth() / 2 then + if frame:GetCenter() < GetScreenWidth() / 2 then + offsetx = frame:GetRight() - GetScreenWidth() / 2 + else + offsetx = frame:GetLeft() - GetScreenWidth() / 2 + end + end + if frame:GetBottom() < 0 then + offsety = frame:GetBottom() + elseif frame:GetTop() and frame:GetTop() > GetScreenHeight() then + offsety = frame:GetTop() - GetScreenHeight() + end + if MainMenuBar:IsVisible() and frame:GetBottom() < MainMenuBar:GetTop() and offsety < frame:GetBottom() - MainMenuBar:GetTop() then + offsety = frame:GetBottom() - MainMenuBar:GetTop() + end + + if FuBar then + local top = 0 + if FuBar then + for i = 1, FuBar:GetNumPanels() do + local panel = FuBar:GetPanel(i) + if panel:GetAttachPoint() == "BOTTOM" then + if panel.frame:GetTop() and panel.frame:GetTop() > top then + top = panel.frame:GetTop() + break + end + end + end + end + if frame:GetBottom() < top and offsety < frame:GetBottom() - top then + offsety = frame:GetBottom() - top + end + local bottom = GetScreenHeight() + if FuBar then + for i = 1, FuBar:GetNumPanels() do + local panel = FuBar:GetPanel(i) + if panel:GetAttachPoint() == "TOP" then + if panel.frame:GetBottom() and panel.frame:GetBottom() < bottom then + bottom = panel.frame:GetBottom() + break + end + end + end + end + if frame:GetTop() > bottom and offsety < frame:GetTop() - bottom then + offsety = frame:GetTop() - bottom + end + end + end + if type(fakeParent) ~= "string" then + frame:SetPoint(point, fakeParent, relativePoint, -offsetx, -offsety) + end + + if detachedData and (info.cantAttach or detachedData.detached) and frame == tooltip then + detachedData.detached = false + frame:Detach() + end + if (not detachedData or not detachedData.detached) and GetMouseFocus() == fakeParent then + self.tooltip.enteredFrame = true + end + overFrame = type(fakeParent) == "table" and MouseIsOver(fakeParent) and fakeParent +end +Tablet.Open = wrap(Tablet.Open, "Tablet:Open") + +function Tablet:Register(parent, ...) + self:argCheck(parent, 2, "table", "string") + if self.registry[parent] then + self:Unregister(parent) + end + local info + local k1 = ... + if type(k1) == "table" and k1[0] then + if type(self.registry[k1]) ~= "table" then + self:error("Other parent not registered") + end + info = copy(self.registry[k1]) + local v1 = select(2, ...) + if type(v1) == "function" then + info.point = v1 + info.relativePoint = nil + end + else + info = new(...) + end + self.registry[parent] = info + info.data = info.data or info.detachedData or {} + info.detachedData = info.detachedData or info.data + local data = info.data + local detachedData = info.detachedData + if not self.onceRegistered[parent] and type(parent) == "table" and type(parent.SetScript) == "function" and not info.dontHook then + if not Dewdrop and AceLibrary:HasInstance("Dewdrop-2.0") then + Dewdrop = AceLibrary("Dewdrop-2.0") + end + local script = parent:GetScript("OnEnter") + parent:SetScript("OnEnter", function(...) + if script then + script(...) + end + if self.registry[parent] then + if (not data or not detachedData.detached) and (Dewdrop and not Dewdrop:IsOpen(parent)) then + self:Open(parent) + end + end + end) + if parent:HasScript("OnMouseDown") then + local script = parent:GetScript("OnMouseDown") + parent:SetScript("OnMouseDown", function(...) + if script then + script(...) + end + if self.registry[parent] and self.registry[parent].tooltip and self.registry[parent].tooltip == self.tooltip then + self.tooltip:Hide() + end + end) + end + if parent:HasScript("OnMouseWheel") then + local script = parent:GetScript("OnMouseWheel") + parent:SetScript("OnMouseWheel", function(...) + if script then + script(...) + end + if self.registry[parent] and self.registry[parent].tooltip then + self.registry[parent].tooltip:Scroll(arg1 < 0) + end + end) + end + end + self.onceRegistered[parent] = true + if GetMouseFocus() == parent then + self:Open(parent) + end +end +Tablet.Register = wrap(Tablet.Register, "Tablet:Register") + +function Tablet:Unregister(parent) + self:argCheck(parent, 2, "table", "string") + if not self.registry[parent] then + self:error("You cannot unregister a parent frame if it has not been registered already.") + end + self.registry[parent] = nil +end +Tablet.Unregister = wrap(Tablet.Unregister, "Tablet:Unregister") + +function Tablet:IsRegistered(parent) + self:argCheck(parent, 2, "table", "string") + return self.registry[parent] and true +end +Tablet.IsRegistered = wrap(Tablet.IsRegistered, "Tablet:IsRegistered") + +local _id = 0 +local addedCategory +local currentCategoryInfo +local depth = 0 +local categoryPool = {} +function CleanCategoryPool(self) + for k,v in pairs(categoryPool) do + del(v) + categoryPool[k] = nil + end + _id = 0 +end + +function Tablet:AddCategory(...) + if not currentFrame then + self:error("You must add categories in within a registration.") + end + local info = new(...) + local cat = currentTabletData:AddCategory(info) + info = del(info) + return cat +end +Tablet.AddCategory = wrap(Tablet.AddCategory, "Tablet:AddCategory") + +function Tablet:SetHint(text) + if not currentFrame then + self:error("You must set hint within a registration.") + end + if currentCategoryInfo then + self:error("You cannot set hint in a category.") + end + currentTabletData:SetHint(text) +end +Tablet.SetHint = wrap(Tablet.SetHint, "Tablet:SetHint") + +function Tablet:SetTitle(text) + if not currentFrame then + self:error("You must set title within a registration.") + end + if currentCategoryInfo then + self:error("You cannot set title in a category.") + end + currentTabletData:SetTitle(text) +end +Tablet.SetTitle = wrap(Tablet.SetTitle, "Tablet:SetTitle") + +function Tablet:SetTitleColor(r, g, b) + if not currentFrame then + self:error("You must set title color within a registration.") + end + if currentCategoryInfo then + self:error("You cannot set title color in a category.") + end + self:argCheck(r, 2, "number") + self:argCheck(g, 3, "number") + self:argCheck(b, 4, "number") + currentTabletData:SetTitleColor(r, g, b) +end +Tablet.SetTitleColor = wrap(Tablet.SetTitleColor, "Tablet:SetTitleColor") + +function Tablet:GetNormalFontSize() + return normalSize +end + +function Tablet:GetHeaderFontSize() + return headerSize +end + +function Tablet:GetNormalFontObject() + return GameTooltipText +end + +function Tablet:GetHeaderFontObject() + return GameTooltipHeaderText +end + +function Tablet:SetFontSizePercent(parent, percent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if info then + if info.tooltip then + info.tooltip:SetFontSizePercent(percent) + else + local data = info.data + local detachedData = info.detachedData + if detachedData.detached then + detachedData.fontSizePercent = percent + else + data.fontSizePercent = percent + end + end + elseif type(parent) == "table" then + parent.fontSizePercent = percent + else + self:error("You cannot change font size with an unregistered parent frame.") + end +end +Tablet.SetFontSizePercent = wrap(Tablet.SetFontSizePercent, "Tablet:SetFontSizePercent") + +function Tablet:GetFontSizePercent(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if info then + local data = info.data + local detachedData = info.detachedData + if detachedData.detached then + return detachedData.fontSizePercent or 1 + else + return data.fontSizePercent or 1 + end + elseif type(parent) == "table" then + return parent.fontSizePercent or 1 + else + self:error("You cannot check font size with an unregistered parent frame.") + end +end +Tablet.GetFontSizePercent = wrap(Tablet.GetFontSizePercent, "Tablet:GetFontSizePercent") + +function Tablet:SetTransparency(parent, percent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if info then + if info.tooltip then + info.tooltip:SetTransparency(percent) + else + local data = info.data + local detachedData = info.detachedData + if detachedData.detached then + detachedData.transparency = percent + elseif data then + data.transparency = percent + end + end + elseif type(parent) == "table" then + parent.transparency = percent + else + self:error("You cannot change transparency with an unregistered parent frame.") + end +end +Tablet.SetTransparency = wrap(Tablet.SetTransparency, "Tablet:SetTransparency") + +function Tablet:GetTransparency(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if info then + local data = info.data + local detachedData = info.detachedData + if detachedData.detached then + return detachedData.transparency or 0.75 + else + return data.transparency or 0.75 + end + elseif type(parent) == "table" then + return parent.transparency or 0.75 + else + self:error("You cannot get transparency with an unregistered parent frame.") + end +end +Tablet.GetTransparency = wrap(Tablet.GetTransparency, "Tablet:GetTransparency") + +function Tablet:SetColor(parent, r, g, b) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if info then + if info.tooltip then + info.tooltip:SetColor(r, g, b) + else + local data = info.data + local detachedData = info.detachedData + if detachedData.detached then + detachedData.r = r + detachedData.g = g + detachedData.b = b + else + data.r = r + data.g = g + data.b = b + end + end + elseif type(parent) == "table" then + parent.r = r + parent.g = g + parent.b = b + else + self:error("You cannot change color with an unregistered parent frame.") + end +end +Tablet.SetColor = wrap(Tablet.SetColor, "Tablet:SetColor") + +function Tablet:GetColor(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if info then + local data = info.data + local detachedData = info.detachedData + if detachedData.detached then + return detachedData.r or 0, detachedData.g or 0, detachedData.b or 0 + else + return data.r or 0, data.g or 0, data.b or 0 + end + elseif type(parent) == "table" then + return parent.r or 0, parent.g or 0, parent.b or 0 + else + self:error("You must provide a registered parent frame to check color") + end +end +Tablet.GetColor = wrap(Tablet.GetColor, "Tablet:GetColor") + +function Tablet:Detach(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot detach tablet with an unregistered parent frame.") + end + if not info.detachedData then + self:error("You cannot detach tablet without a data field.") + end + if info.tooltip and info.tooltip == tooltip then + tooltip:Detach() + else + info.detachedData.detached = true + local detached = AcquireDetachedFrame(self, info, info.data, info.detachedData) + + detached.menu = info.menu + local children = info.children + function detached:children() + if not self.preventRefresh then + currentFrame = self + currentTabletData = TabletData:new(self) + self:ClearLines() + if children then + children() + end + currentTabletData:Display(currentFrame) + self:Show(currentTabletData) + currentTabletData:del() + currentTabletData = nil + currentFrame = nil + end + end + detached:SetOwner(parent) + detached:children() + end +end +Tablet.Detach = wrap(Tablet.Detach, "Tablet:Detach") + +function Tablet:Attach(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot detach tablet with an unregistered parent frame.") + end + if not info.detachedData then + self:error("You cannot attach tablet without a data field.") + end + if info.tooltip and info.tooltip ~= tooltip then + info.tooltip:Attach() + else + info.detachedData.detached = false + end +end +Tablet.Attach = wrap(Tablet.Attach, "Tablet:Attach") + +function Tablet:IsAttached(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot check tablet with an unregistered parent frame.") + end + return not info.detachedData or not info.detachedData.detached +end +Tablet.IsAttached = wrap(Tablet.IsAttached, "Tablet:IsAttached") + +function Tablet:Refresh(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot refresh tablet with an unregistered parent frame.") + end + local tt = info.tooltip + if tt and not tt.preventRefresh and tt:IsShown() then + tt.updating = true + tt:children() + tt.updating = false + end +end +Tablet.Refresh = wrap(Tablet.Refresh, "Tablet:Refresh") + +function Tablet:IsLocked(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot check tablet with an unregistered parent frame.") + end + return info.detachedData and info.detachedData.locked +end +Tablet.IsLocked = wrap(Tablet.IsLocked, "Tablet:IsLocked") + +function Tablet:ToggleLocked(parent) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot lock tablet with an unregistered parent frame.") + end + if info.tooltip and info.tooltip ~= tooltip then + info.tooltip:Lock() + elseif info.detachedData then + info.detachedData.locked = info.detachedData.locked + end +end +Tablet.ToggleLocked = wrap(Tablet.ToggleLocked, "Tablet:ToggleLocked") + +function Tablet:UpdateDetachedData(parent, detachedData) + self:argCheck(parent, 2, "table", "string") + local info = self.registry[parent] + if not info then + self:error("You cannot update tablet with an unregistered parent frame.") + end + self:argCheck(detachedData, 3, "table") + if info.data == info.detachedData then + info.data = detachedData + end + info.detachedData = detachedData + if info.detachedData.detached then + self:Detach(parent) + elseif info.tooltip and info.tooltip.owner then + self:Attach(parent) + end +end +Tablet.UpdateDetachedData = wrap(Tablet.UpdateDetachedData, "Tablet:UpdateDetachedData") + +if DEBUG then + function Tablet:ListProfileInfo() + local duration, times, memories = GetProfileInfo() + self:assert(duration and times and memories) + local t = new() + for method in pairs(memories) do + table.insert(t, method) + end + table.sort(t, function(alpha, bravo) + if memories[alpha] ~= memories[bravo] then + return memories[alpha] < memories[bravo] + elseif times[alpha] ~= times[bravo] then + return times[alpha] < times[bravo] + else + return alpha < bravo + end + end) + local memory = 0 + local time = 0 + for _,method in ipairs(t) do + DEFAULT_CHAT_FRAME:AddMessage(format("%s || %.3f s || %.3f%% || %d KiB", method, times[method], times[method] / duration * 100, memories[method])) + memory = memory + memories[method] + time = time + times[method] + end + DEFAULT_CHAT_FRAME:AddMessage(format("%s || %.3f s || %.3f%% || %d KiB", "Total", time, time / duration * 100, memory)) + del(t) + end + SLASH_TABLET1 = "/tablet" + SLASH_TABLET2 = "/tabletlib" + SlashCmdList["TABLET"] = function(msg) + TabletLib:GetInstance(MAJOR_VERSION):ListProfileInfo() + end +end + +local function activate(self, oldLib, oldDeactivate) + Tablet = self + if oldLib then + self.registry = oldLib.registry + self.onceRegistered = oldLib.onceRegistered + self.tooltip = oldLib.tooltip + else + self.registry = {} + self.onceRegistered = {} + end + + tooltip = self.tooltip + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +local function deactivate(self) + StopCheckingAlt() +end + +AceLibrary:Register(Tablet, MAJOR_VERSION, MINOR_VERSION, activate, deactivate) diff --git a/sounds/Heartbeat.wav b/sounds/Heartbeat.wav new file mode 100644 index 0000000..97a5c74 Binary files /dev/null and b/sounds/Heartbeat.wav differ diff --git a/sounds/blareaggro.wav b/sounds/blareaggro.wav new file mode 100644 index 0000000..60b0d1a Binary files /dev/null and b/sounds/blareaggro.wav differ diff --git a/sounds/blaredanger.wav b/sounds/blaredanger.wav new file mode 100644 index 0000000..81e0e3b Binary files /dev/null and b/sounds/blaredanger.wav differ diff --git a/sounds/blaredruid.wav b/sounds/blaredruid.wav new file mode 100644 index 0000000..2cc7ff6 Binary files /dev/null and b/sounds/blaredruid.wav differ diff --git a/sounds/blarehunter.wav b/sounds/blarehunter.wav new file mode 100644 index 0000000..b6e1042 Binary files /dev/null and b/sounds/blarehunter.wav differ diff --git a/sounds/blaremage.wav b/sounds/blaremage.wav new file mode 100644 index 0000000..074ca07 Binary files /dev/null and b/sounds/blaremage.wav differ diff --git a/sounds/blarepaladin.wav b/sounds/blarepaladin.wav new file mode 100644 index 0000000..bd8d5a1 Binary files /dev/null and b/sounds/blarepaladin.wav differ diff --git a/sounds/blarepriest.wav b/sounds/blarepriest.wav new file mode 100644 index 0000000..a59d08a Binary files /dev/null and b/sounds/blarepriest.wav differ diff --git a/sounds/blarerogue.wav b/sounds/blarerogue.wav new file mode 100644 index 0000000..17c129c Binary files /dev/null and b/sounds/blarerogue.wav differ diff --git a/sounds/blareshaman.wav b/sounds/blareshaman.wav new file mode 100644 index 0000000..53be7bf Binary files /dev/null and b/sounds/blareshaman.wav differ diff --git a/sounds/blarewarlock.wav b/sounds/blarewarlock.wav new file mode 100644 index 0000000..c6963c9 Binary files /dev/null and b/sounds/blarewarlock.wav differ diff --git a/sounds/blarewarrior.wav b/sounds/blarewarrior.wav new file mode 100644 index 0000000..b878f9f Binary files /dev/null and b/sounds/blarewarrior.wav differ diff --git a/sounds/blip5.wav b/sounds/blip5.wav new file mode 100644 index 0000000..55d6dea Binary files /dev/null and b/sounds/blip5.wav differ diff --git a/sounds/blip5druid.wav b/sounds/blip5druid.wav new file mode 100644 index 0000000..f13d8a0 Binary files /dev/null and b/sounds/blip5druid.wav differ diff --git a/sounds/blip5hunter.wav b/sounds/blip5hunter.wav new file mode 100644 index 0000000..48a8a7a Binary files /dev/null and b/sounds/blip5hunter.wav differ diff --git a/sounds/blip5mage.wav b/sounds/blip5mage.wav new file mode 100644 index 0000000..b9b33ca Binary files /dev/null and b/sounds/blip5mage.wav differ diff --git a/sounds/blip5paladin.wav b/sounds/blip5paladin.wav new file mode 100644 index 0000000..ab2e658 Binary files /dev/null and b/sounds/blip5paladin.wav differ diff --git a/sounds/blip5priest.wav b/sounds/blip5priest.wav new file mode 100644 index 0000000..aa01aba Binary files /dev/null and b/sounds/blip5priest.wav differ diff --git a/sounds/blip5rogue.wav b/sounds/blip5rogue.wav new file mode 100644 index 0000000..bf0bb4b Binary files /dev/null and b/sounds/blip5rogue.wav differ diff --git a/sounds/blip5shaman.wav b/sounds/blip5shaman.wav new file mode 100644 index 0000000..0921ddb Binary files /dev/null and b/sounds/blip5shaman.wav differ diff --git a/sounds/blip5warlock.wav b/sounds/blip5warlock.wav new file mode 100644 index 0000000..0829820 Binary files /dev/null and b/sounds/blip5warlock.wav differ diff --git a/sounds/blip5warrior.wav b/sounds/blip5warrior.wav new file mode 100644 index 0000000..8b127c4 Binary files /dev/null and b/sounds/blip5warrior.wav differ diff --git a/sounds/phasers3.wav b/sounds/phasers3.wav new file mode 100644 index 0000000..0222b03 Binary files /dev/null and b/sounds/phasers3.wav differ diff --git a/sounds/phasers3druid.wav b/sounds/phasers3druid.wav new file mode 100644 index 0000000..4fb4b84 Binary files /dev/null and b/sounds/phasers3druid.wav differ diff --git a/sounds/phasers3hunter.wav b/sounds/phasers3hunter.wav new file mode 100644 index 0000000..cb928f1 Binary files /dev/null and b/sounds/phasers3hunter.wav differ diff --git a/sounds/phasers3mage.wav b/sounds/phasers3mage.wav new file mode 100644 index 0000000..9ae4097 Binary files /dev/null and b/sounds/phasers3mage.wav differ diff --git a/sounds/phasers3paladin.wav b/sounds/phasers3paladin.wav new file mode 100644 index 0000000..a6dafe0 Binary files /dev/null and b/sounds/phasers3paladin.wav differ diff --git a/sounds/phasers3priest.wav b/sounds/phasers3priest.wav new file mode 100644 index 0000000..03677ad Binary files /dev/null and b/sounds/phasers3priest.wav differ diff --git a/sounds/phasers3rogue.wav b/sounds/phasers3rogue.wav new file mode 100644 index 0000000..d56c06a Binary files /dev/null and b/sounds/phasers3rogue.wav differ diff --git a/sounds/phasers3shaman.wav b/sounds/phasers3shaman.wav new file mode 100644 index 0000000..0796a7b Binary files /dev/null and b/sounds/phasers3shaman.wav differ diff --git a/sounds/phasers3warlock.wav b/sounds/phasers3warlock.wav new file mode 100644 index 0000000..455317a Binary files /dev/null and b/sounds/phasers3warlock.wav differ diff --git a/sounds/phasers3warrior.wav b/sounds/phasers3warrior.wav new file mode 100644 index 0000000..a69c8f5 Binary files /dev/null and b/sounds/phasers3warrior.wav differ diff --git a/sounds/shrink.wav b/sounds/shrink.wav new file mode 100644 index 0000000..d60baf3 Binary files /dev/null and b/sounds/shrink.wav differ diff --git a/sounds/shrinkdruid.wav b/sounds/shrinkdruid.wav new file mode 100644 index 0000000..e57f7c1 Binary files /dev/null and b/sounds/shrinkdruid.wav differ diff --git a/sounds/shrinkhunter.wav b/sounds/shrinkhunter.wav new file mode 100644 index 0000000..d93e17e Binary files /dev/null and b/sounds/shrinkhunter.wav differ diff --git a/sounds/shrinkmage.wav b/sounds/shrinkmage.wav new file mode 100644 index 0000000..5cdf0de Binary files /dev/null and b/sounds/shrinkmage.wav differ diff --git a/sounds/shrinkpaladin.wav b/sounds/shrinkpaladin.wav new file mode 100644 index 0000000..e71e7be Binary files /dev/null and b/sounds/shrinkpaladin.wav differ diff --git a/sounds/shrinkpriest.wav b/sounds/shrinkpriest.wav new file mode 100644 index 0000000..266884b Binary files /dev/null and b/sounds/shrinkpriest.wav differ diff --git a/sounds/shrinkshaman.wav b/sounds/shrinkshaman.wav new file mode 100644 index 0000000..a915f07 Binary files /dev/null and b/sounds/shrinkshaman.wav differ diff --git a/sounds/shrinkwarlock.wav b/sounds/shrinkwarlock.wav new file mode 100644 index 0000000..672b111 Binary files /dev/null and b/sounds/shrinkwarlock.wav differ