You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
984 lines
31 KiB
984 lines
31 KiB
local ADDON_NAME, ns = ...
|
|
BetterAddonListDB = BetterAddonListDB or {}
|
|
|
|
local LibDialog = LibStub("LibDialog-1.0n")
|
|
|
|
local _G = _G
|
|
local GetAddOnMetadata = C_AddOns.GetAddOnMetadata
|
|
local wow_110 = AddonList.ForceLoad and true
|
|
|
|
local L = ns.L
|
|
L.LOAD_ADDON = GetLocale() == "ruRU" and "Загрузить" or LOAD_ADDON
|
|
|
|
local UpdateList
|
|
|
|
local sets = nil
|
|
local included = nil
|
|
local character = UnitGUID("player")
|
|
|
|
local function IsAddonProtected(index)
|
|
if not index then return end
|
|
local name, _, _, _, _, security = C_AddOns.GetAddOnInfo(index)
|
|
return name == ADDON_NAME or security == "SECURE" or BetterAddonListDB.protected[tostring(name)]
|
|
end
|
|
|
|
local function SetAddonProtected(index, value)
|
|
if not index then return end
|
|
local name, _, _, _, _, security = C_AddOns.GetAddOnInfo(index)
|
|
if name ~= ADDON_NAME and security == "INSECURE" then
|
|
BetterAddonListDB.protected[tostring(name)] = value and true or nil
|
|
end
|
|
end
|
|
|
|
local function CheckAddonDependencies(...)
|
|
for i = 1, select("#", ...) do
|
|
local dep = select(i, ...)
|
|
if C_AddOns.GetAddOnEnableState(dep, character) == 0 then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
|
|
local addon = CreateFrame("Frame")
|
|
addon:SetScript("OnEvent", function(self, event, ...) self[event](self, ...) end)
|
|
addon:RegisterEvent("ADDON_LOADED")
|
|
addon:RegisterEvent("PLAYER_LOGIN")
|
|
addon:RegisterEvent("PLAYER_LOGOUT")
|
|
|
|
function addon:ADDON_LOADED(addon_name)
|
|
if addon_name ~= ADDON_NAME then return end
|
|
self:UnregisterEvent("ADDON_LOADED")
|
|
|
|
if not BetterAddonListDB.sets then
|
|
BetterAddonListDB.sets = {}
|
|
end
|
|
sets = BetterAddonListDB.sets
|
|
|
|
if not BetterAddonListDB.nested then
|
|
BetterAddonListDB.nested = {}
|
|
end
|
|
included = BetterAddonListDB.nested
|
|
|
|
if not BetterAddonListDB.protected then
|
|
BetterAddonListDB.protected = {}
|
|
end
|
|
|
|
|
|
--- From BlizzBugsSuck:
|
|
-- Fix glitchy-ness of EnableAddOn/DisableAddOn API, which affects the stability of the default
|
|
-- UI's addon management list (both in-game and glue), as well as any addon-management addons.
|
|
-- The problem is caused by broken defaulting logic used to merge AddOns.txt settings across
|
|
-- characters to those missing a setting in AddOns.txt, whereby toggling an addon for a single character
|
|
-- sometimes results in also toggling it for a different character on that realm for no obvious reason.
|
|
-- The code below ensures each character gets an independent enable setting for each installed
|
|
-- addon in its AddOns.txt file, thereby avoiding the broken defaulting logic.
|
|
-- Note the fix applies to each character the first time it loads there, and a given character
|
|
-- is not protected from the faulty logic on addon X until after the fix has run with addon X
|
|
-- installed (regardless of enable setting) and the character has logged out normally.
|
|
|
|
-- XXX Using this to fix some enabled state stuff: startStatus and outOfDate
|
|
local hasOutOfDate = false
|
|
for i = 1, C_AddOns.GetNumAddOns() do
|
|
local _, _, _, loadable, reason = C_AddOns.GetAddOnInfo(i)
|
|
local enabled = C_AddOns.GetAddOnEnableState(i, character) > 0
|
|
if enabled and not loadable and reason == "INTERFACE_VERSION" then
|
|
hasOutOfDate = true
|
|
end
|
|
AddonList.startStatus[i] = enabled
|
|
if enabled then
|
|
C_AddOns.EnableAddOn(i, character)
|
|
else
|
|
C_AddOns.DisableAddOn(i, character)
|
|
end
|
|
end
|
|
AddonList.outOfDate = C_AddOns.IsAddonVersionCheckEnabled() and hasOutOfDate
|
|
|
|
hooksecurefunc(C_AddOns, "DisableAllAddOns", function()
|
|
self:EnableProtected()
|
|
end)
|
|
|
|
-- check protected
|
|
local messages = {}
|
|
for name in next, BetterAddonListDB.protected do
|
|
local _, _, _, loadable, reason = C_AddOns.GetAddOnInfo(name)
|
|
if C_AddOns.IsAddOnLoadOnDemand(name) then
|
|
if not CheckAddonDependencies(C_AddOns.GetAddOnDependencies(name)) then
|
|
loadable, reason = false, "DEP_DISABLED"
|
|
elseif not loadable and reason == "DEMAND_LOADED" then
|
|
loadable = true
|
|
end
|
|
end
|
|
if not loadable then
|
|
if reason == "MISSING" then
|
|
BetterAddonListDB.protected[name] = nil
|
|
else
|
|
messages[name] = reason or "UNKNOWN_ERROR"
|
|
end
|
|
end
|
|
C_AddOns.EnableAddOn(name)
|
|
end
|
|
if next(messages) then
|
|
C_Timer.After(12, function()
|
|
local ood, dep = nil, nil
|
|
for name, reason in next, messages do
|
|
self:Print(L["Problem with protected addon %q (%s)"]:format(name, _G["ADDON_"..reason]))
|
|
if reason == "INTERFACE_VERSION" then
|
|
ood = true
|
|
elseif reason:find("DEP", nil, true) then
|
|
dep = true
|
|
end
|
|
end
|
|
if ood and C_AddOns.IsAddonVersionCheckEnabled() then
|
|
self:Print(L["Out of date addons are being disabled! They will not be able to load until their interface version is updated or \"Load out of date AddOns\" is checked."])
|
|
elseif not dep then
|
|
self:Print(L["Reload UI to load these addons."])
|
|
end
|
|
messages = nil
|
|
end)
|
|
end
|
|
end
|
|
|
|
function addon:PLAYER_LOGIN()
|
|
-- make the panel movable
|
|
local mover = CreateFrame("Frame", "BetterAddonListMover", AddonList)
|
|
mover:EnableMouse(true)
|
|
mover:SetPoint("TOP", AddonList, "TOP", 0, 0)
|
|
mover:SetWidth(500)
|
|
mover:SetHeight(25)
|
|
mover:SetScript("OnMouseDown", function() AddonList:StartMoving() end)
|
|
mover:SetScript("OnMouseUp", function() AddonList:StopMovingOrSizing() end)
|
|
AddonList:SetMovable(true)
|
|
AddonList:ClearAllPoints()
|
|
AddonList:SetPoint("CENTER")
|
|
|
|
-- move and resize the "Load out of date addons" check box
|
|
local forceLoad = AddonListForceLoad or AddonList.ForceLoad
|
|
forceLoad:ClearAllPoints()
|
|
forceLoad:SetPoint("TOPLEFT", AddonList, 4, 0)
|
|
forceLoad:SetFrameLevel(AddonList.TitleContainer:GetFrameLevel() + 1)
|
|
forceLoad:SetSize(24, 24)
|
|
local regions = {forceLoad:GetRegions()}
|
|
for _, region in ipairs(regions) do
|
|
if region:IsObjectType("FontString") then
|
|
region:SetPoint("LEFT", forceLoad, "RIGHT", 2, 0)
|
|
region:SetText(L["Load out of date"])
|
|
break
|
|
end
|
|
end
|
|
|
|
if not wow_110 then
|
|
-- add some option buttons
|
|
local hideIcons = CreateFrame("Button", "BetterAddonListOptionHideIcons", AddonList)
|
|
hideIcons:SetFrameLevel(AddonList.TitleContainer:GetFrameLevel() + 1)
|
|
hideIcons:SetNormalTexture(134400) -- inv_misc_questionmark
|
|
hideIcons:SetHighlightTexture(134400)
|
|
hideIcons:SetSize(20, 20)
|
|
hideIcons:SetPoint("LEFT", AddonListDisableAllButton, "RIGHT", 25, -1)
|
|
hideIcons:SetScript("OnClick", function(this)
|
|
-- cycle through
|
|
if BetterAddonListDB.hide_icons == false then
|
|
BetterAddonListDB.hide_icons = true -- true = hide
|
|
elseif BetterAddonListDB.hide_icons == true then
|
|
BetterAddonListDB.hide_icons = nil -- nil = nodefault
|
|
elseif BetterAddonListDB.hide_icons == nil then
|
|
BetterAddonListDB.hide_icons = false --- false = show all
|
|
end
|
|
UpdateList()
|
|
end)
|
|
hideIcons:SetScript("OnEnter", function(this)
|
|
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT")
|
|
GameTooltip:SetText(L["Toggle Icons"])
|
|
GameTooltip:Show()
|
|
end)
|
|
hideIcons:SetScript("OnLeave", GameTooltip_Hide)
|
|
|
|
local hideMemory = CreateFrame("Button", "BetterAddonListOptionHideIcons", AddonList)
|
|
hideMemory:SetFrameLevel(AddonList.TitleContainer:GetFrameLevel() + 1)
|
|
hideMemory:SetNormalTexture(4555550) -- inv_10_jewelcrafting_gem1leveling_cut_green
|
|
hideMemory:SetHighlightTexture(4555550)
|
|
hideMemory:SetSize(20, 20)
|
|
hideMemory:SetPoint("LEFT", hideIcons, "RIGHT", 2, 0)
|
|
hideMemory:SetScript("OnClick", function(this)
|
|
BetterAddonListDB.hide_memory = not BetterAddonListDB.hide_memory or nil
|
|
UpdateList()
|
|
end)
|
|
hideMemory:SetScript("OnEnter", function(this)
|
|
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT")
|
|
GameTooltip:SetText(L["Toggle Memory Usage"])
|
|
GameTooltip:Show()
|
|
end)
|
|
hideMemory:SetScript("OnLeave", GameTooltip_Hide)
|
|
end
|
|
|
|
-- let the frame overlap over ui frames
|
|
--UIPanelWindows["AddonList"].area = nil
|
|
|
|
SLASH_BETTERADDONLIST1 = "/addons"
|
|
SLASH_BETTERADDONLIST2 = "/acp" -- muscle memory ;[
|
|
SLASH_BETTERADDONLIST3 = "/bal" -- why not
|
|
SlashCmdList["BETTERADDONLIST"] = function(input)
|
|
if not input or input:trim() == "" then
|
|
ShowUIPanel(AddonList)
|
|
return
|
|
end
|
|
|
|
input = SecureCmdOptionParse(input)
|
|
if not input then return end
|
|
|
|
local command, rest = input:match("^(%S*)%s*(.-)$")
|
|
command = command and command:lower()
|
|
rest = (rest and rest ~= "") and rest:trim() or nil
|
|
|
|
if command == "load" then
|
|
if sets[rest] then
|
|
self:LoadSet(rest)
|
|
self:Print(L["Enabled only addons in set %q."]:format(rest))
|
|
else
|
|
self:Print(L["No set named %q."]:format(rest))
|
|
end
|
|
elseif command == "unload" or command == "disable" then
|
|
if sets[rest] then
|
|
self:DisableSet(rest)
|
|
self:Print(L["Disabled addons in set %q."]:format(rest))
|
|
else
|
|
self:Print(L["No set named %q."]:format(rest))
|
|
end
|
|
elseif command == "enable" then
|
|
if sets[rest] then
|
|
self:EnableSet(rest)
|
|
self:Print(L["Enabled addons in set %q."]:format(rest))
|
|
else
|
|
self:Print(L["No set named %q."]:format(rest))
|
|
end
|
|
elseif command == "save" then
|
|
self:SaveSet(rest)
|
|
self:Print(L["Saved enabled addons to set %q."]:format(rest))
|
|
elseif command == "delete" then
|
|
if sets[rest] then
|
|
self:DeleteSet(rest)
|
|
self:Print(L["Deleted set %q."]:format(rest))
|
|
else
|
|
self:Print(L["No set named %q."]:format(rest))
|
|
end
|
|
elseif command == "disableall" then
|
|
C_AddOns.DisableAllAddOns(character)
|
|
UpdateList()
|
|
self:Print(L["Disabled all addons."])
|
|
elseif command == "reset" then
|
|
C_AddOns.ResetAddOns()
|
|
UpdateList()
|
|
self:Print(L["Reset addons to what was enabled at login."])
|
|
end
|
|
end
|
|
end
|
|
|
|
function addon:PLAYER_LOGOUT()
|
|
-- clean up
|
|
for k, v in next, included do
|
|
if not next(v) then
|
|
included[k] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function addon:Print(...)
|
|
print("|cff33ff99BetterAddonList|r:", tostringall(...))
|
|
end
|
|
|
|
LibDialog:Register("BETTER_ADDONLIST_SAVESET", {
|
|
buttons = {
|
|
{ text = OKAY, on_click = function(self, data) addon:SaveSet(data) end, },
|
|
{ text = CANCEL, },
|
|
},
|
|
on_show = function(self, data)
|
|
self.text:SetFormattedText(L["Save the currently selected addons to %s?"], NORMAL_FONT_COLOR:WrapTextInColorCode(data))
|
|
CloseDropDownMenus(1)
|
|
end,
|
|
no_close_button = true,
|
|
hide_on_escape = true,
|
|
show_while_dead = true,
|
|
})
|
|
|
|
LibDialog:Register("BETTER_ADDONLIST_DELETESET", {
|
|
buttons = {
|
|
{ text = OKAY, on_click = function(self, data) addon:DeleteSet(data) end, },
|
|
{ text = CANCEL, },
|
|
},
|
|
on_show = function(self, data)
|
|
self.text:SetFormattedText(L["Delete set %s?"], NORMAL_FONT_COLOR:WrapTextInColorCode(data))
|
|
CloseDropDownMenus(1)
|
|
end,
|
|
no_close_button = true,
|
|
hide_on_escape = true,
|
|
show_while_dead = true,
|
|
})
|
|
|
|
LibDialog:Register("BETTER_ADDONLIST_NEWSET", {
|
|
text = L["Enter the name for the new set"],
|
|
buttons = {
|
|
{
|
|
text = OKAY,
|
|
on_click = function(self)
|
|
local text = self.editboxes[1]:GetText():trim()
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_NEWSET")
|
|
if sets[text] then
|
|
LibDialog:Spawn("BETTER_ADDONLIST_ERROR_NAME", {text, "BETTER_ADDONLIST_NEWSET"})
|
|
return true
|
|
end
|
|
addon:SaveSet(text)
|
|
end,
|
|
},
|
|
{ text = CANCEL, },
|
|
},
|
|
editboxes = {
|
|
{
|
|
on_enter_pressed = function(editbox)
|
|
local text = editbox:GetText():trim()
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_NEWSET")
|
|
if sets[text] then
|
|
LibDialog:Spawn("BETTER_ADDONLIST_ERROR_NAME", {text, "BETTER_ADDONLIST_NEWSET"})
|
|
return true
|
|
end
|
|
addon:SaveSet(text)
|
|
end,
|
|
on_escape_pressed = function()
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_NEWSET")
|
|
end,
|
|
auto_focus = true,
|
|
},
|
|
},
|
|
on_show = function(self) CloseDropDownMenus(1) end,
|
|
no_close_button = true,
|
|
hide_on_escape = true,
|
|
show_while_dead = true,
|
|
})
|
|
|
|
LibDialog:Register("BETTER_ADDONLIST_RENAMESET", {
|
|
buttons = {
|
|
{
|
|
text = OKAY,
|
|
on_click = function(self, data)
|
|
local text = self.editboxes[1]:GetText():trim()
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_RENAMESET")
|
|
if sets[text] then
|
|
LibDialog:Spawn("BETTER_ADDONLIST_ERROR_NAME", {text, "BETTER_ADDONLIST_RENAMESET", data})
|
|
return true
|
|
end
|
|
addon:RenameSet(data, text)
|
|
end,
|
|
},
|
|
{ text = CANCEL, },
|
|
},
|
|
editboxes = {
|
|
{
|
|
on_enter_pressed = function(editbox, data)
|
|
local text = editbox:GetText():trim()
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_RENAMESET")
|
|
if sets[text] then
|
|
LibDialog:Spawn("BETTER_ADDONLIST_ERROR_NAME", {text, "BETTER_ADDONLIST_RENAMESET", data})
|
|
return true
|
|
end
|
|
addon:RenameSet(data, text)
|
|
end,
|
|
on_escape_pressed = function()
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_RENAMESET")
|
|
end,
|
|
auto_focus = true,
|
|
},
|
|
},
|
|
on_show = function(self, data)
|
|
self.text:SetFormattedText(L["Enter the new name for %s"], NORMAL_FONT_COLOR:WrapTextInColorCode(data))
|
|
CloseDropDownMenus(1)
|
|
end,
|
|
no_close_button = true,
|
|
hide_on_escape = true,
|
|
show_while_dead = true,
|
|
})
|
|
|
|
LibDialog:Register("BETTER_ADDONLIST_ERROR_NAME", {
|
|
buttons = {
|
|
{
|
|
text = OKAY,
|
|
on_click = function(self, data)
|
|
LibDialog:Dismiss("BETTER_ADDONLIST_ERROR_NAME")
|
|
LibDialog:Spawn(data[2], data[3])
|
|
return true
|
|
end,
|
|
},
|
|
{ text = CANCEL, },
|
|
},
|
|
on_show = function(self, data)
|
|
self.text:SetFormattedText(L["There is already a set named \"%s\".\nPlease choose another name."], NORMAL_FONT_COLOR:WrapTextInColorCode(data[1]))
|
|
end,
|
|
icon = _G.STATICPOPUP_TEXTURE_ALERT,
|
|
-- cancels_on_spawn = { "BETTER_ADDONLIST_NEWSET", "BETTER_ADDONLIST_RENAMESET" },
|
|
no_close_button = true,
|
|
hide_on_escape = true,
|
|
show_while_dead = true,
|
|
})
|
|
|
|
-- sets menu
|
|
do
|
|
-- pseudo natural sorting
|
|
local function pad(s)
|
|
local n = tonumber(s)
|
|
if n < 1000 then
|
|
return ("%04d"):format(n)
|
|
end
|
|
return s
|
|
end
|
|
local function natsort(a, b)
|
|
return a:gsub("(%d+)", pad):lower() < b:gsub("(%d+)", pad):lower()
|
|
end
|
|
local function icmp(a, b) -- ignore color
|
|
return a:gsub("(%|c%x%x%x%x%x%x%x%x|%|r)", "") < b:gsub("(%|c%x%x%x%x%x%x%x%x|%|r)", "") -- =~ s/(\|c[a-fA-F0-9]{8}|\|r)//g
|
|
end
|
|
|
|
local function IsSetIncluded(data)
|
|
local setA, setB = data[1], data[2]
|
|
return included[setA] and included[setA][setB] and true
|
|
end
|
|
|
|
local function SetSetIncluded(data)
|
|
local value = not IsSetIncluded(data)
|
|
local setA, setB = data[1], data[2]
|
|
if not included[setA] then
|
|
included[setA] = {}
|
|
end
|
|
included[setA][setB] = value or nil
|
|
end
|
|
|
|
local function GenerateSetsMenu(owner, root)
|
|
if next(sets) then
|
|
local list = {}
|
|
for name in next, sets do
|
|
list[#list + 1] = name
|
|
end
|
|
sort(list, natsort)
|
|
|
|
for _, currentSet in ipairs(list) do
|
|
local set = root:CreateButton(currentSet)
|
|
set:CreateTitle(currentSet)
|
|
|
|
local count = #sets[currentSet]
|
|
local view = set:CreateButton(L["View (%d)"]:format(count))
|
|
view:SetEnabled(count > 0)
|
|
if count > 0 then
|
|
view:SetScrollMode(50 * 8)
|
|
view:CreateTitle(L["Addon List"])
|
|
sort(sets[currentSet], icmp)
|
|
for _, addonName in ipairs(sets[currentSet]) do
|
|
local _, _, _, loadable, reason = C_AddOns.GetAddOnInfo(addonName)
|
|
view:CreateButton(addonName):SetEnabled(loadable or reason ~= "MISSING")
|
|
end
|
|
end
|
|
|
|
set:CreateDivider()
|
|
|
|
set:CreateButton(L["Load"], function(data) addon:LoadSet(data) end, currentSet):SetTooltip(function(tooltip)
|
|
tooltip:SetText(L["Load"], 1.0, 1.0, 1.0, 1.0, true)
|
|
tooltip:AddLine(L["Disable all addons then enable addons in this set."], 1.0, 0.82, 0.0, true)
|
|
end)
|
|
set:CreateButton(L["Save"], function(data) LibDialog:Spawn("BETTER_ADDONLIST_SAVESET", data) end, currentSet)
|
|
set:CreateButton(L["Rename"], function(data) LibDialog:Spawn("BETTER_ADDONLIST_RENAMESET", data) end, currentSet)
|
|
set:CreateButton(L["Delete"], function(data) LibDialog:Spawn("BETTER_ADDONLIST_DELETESET", data) end, currentSet)
|
|
|
|
set:CreateDivider()
|
|
|
|
local includeSets = set:CreateButton(L["Enabled with this set"])
|
|
includeSets:SetEnabled(#list > 1) -- have more than the current set
|
|
-- includeSets:CreateTitle(L["Additionally enable addons from these sets"])
|
|
for _, name in ipairs(list) do
|
|
if name ~= currentSet then
|
|
includeSets:CreateCheckbox(name, IsSetIncluded, SetSetIncluded, {currentSet, name})
|
|
end
|
|
end
|
|
|
|
set:CreateDivider()
|
|
|
|
set:CreateButton(L["Enable addons from this set"], function(data) addon:EnableSet(data) end, currentSet)
|
|
set:CreateButton(L["Disable addons from this set"], function(data) addon:DisableSet(data) end, currentSet)
|
|
end
|
|
|
|
root:CreateDivider()
|
|
end
|
|
|
|
root:CreateButton(L["Create new set"], function() LibDialog:Spawn("BETTER_ADDONLIST_NEWSET") end)
|
|
root:CreateButton(L["Reset"], function()
|
|
C_AddOns.ResetAddOns()
|
|
UpdateList()
|
|
end)
|
|
end
|
|
|
|
local button = CreateFrame("Button", "BetterAddonListSetsButton", AddonList, "UIPanelButtonTemplate")
|
|
button:SetPoint("LEFT", AddonList.Dropdown, "RIGHT", 3, 0)
|
|
button:SetSize(80, 22)
|
|
button:SetText(L["Sets"])
|
|
-- button:SetScript("OnClick", function(self)
|
|
-- MenuUtil.CreateContextMenu(self, GenerateSetsMenu)
|
|
-- end)
|
|
|
|
-- because I want the old ToggleDropDownMenu behaviour with a context menu x.x
|
|
_G.Mixin(button, _G.DropdownButtonMixin)
|
|
button.menuRelativePoint = "BOTTOMLEFT"
|
|
button.menuPointX = 6
|
|
button.menuPointY = 2
|
|
button.menuGenerator = GenerateSetsMenu
|
|
button:OnLoad_Intrinsic()
|
|
button:SetScript("OnMouseDown", button.OnMouseDown_Intrinsic)
|
|
-- button:EnableRegenerateOnResponse() -- rebuild the menu to pick up included set changes
|
|
end
|
|
|
|
-- lock icon toggle / memory usage
|
|
do
|
|
local function OnClick(lock, button, down)
|
|
if IsShiftKeyDown() and button == "LeftButton" then
|
|
local index = lock:GetParent():GetID()
|
|
SetAddonProtected(index, not IsAddonProtected(index))
|
|
AddonList_Enable(index, true)
|
|
end
|
|
end
|
|
|
|
local updater = CreateFrame("Frame", nil, AddonList)
|
|
updater:SetScript("OnShow", function(self)
|
|
UpdateAddOnMemoryUsage()
|
|
end)
|
|
|
|
local function buildDeps(...)
|
|
local deps = ""
|
|
for i = 1, select("#", ...) do
|
|
local dep = select(i, ...)
|
|
if C_AddOns.GetAddOnEnableState(dep, character) == 0 then
|
|
dep = ("|cffff2020%s|r"):format(dep)
|
|
end
|
|
if i == 1 then
|
|
deps = _G.ADDON_DEPENDENCIES .. dep
|
|
else
|
|
deps = deps .. ", " .. dep
|
|
end
|
|
end
|
|
return deps
|
|
end
|
|
|
|
if not wow_110 then
|
|
AddonTooltip_Update = function(owner)
|
|
local index = owner:GetID()
|
|
if not index or index < 1 or index > C_AddOns.GetNumAddOns() then return end
|
|
|
|
local name, title, notes, _, _, security = C_AddOns.GetAddOnInfo(index)
|
|
GameTooltip:ClearLines()
|
|
if security == "BANNED" then
|
|
GameTooltip:SetText(ADDON_BANNED_TOOLTIP)
|
|
else
|
|
local version = GetAddOnMetadata(index, "Version")
|
|
if version and version ~= "1.3.1" then
|
|
GameTooltip:AddDoubleLine(title or name, version)
|
|
else
|
|
GameTooltip:AddLine(title or name)
|
|
end
|
|
GameTooltip:AddLine(notes, 1.0, 1.0, 1.0, true)
|
|
GameTooltip:AddLine(buildDeps(C_AddOns.GetAddOnDependencies(index)))
|
|
|
|
local memory = owner.memoryUsage
|
|
if memory then
|
|
local text
|
|
if memory > 1000 then
|
|
memory = memory / 1000
|
|
text = L["Memory: %.02f MB"]:format(memory)
|
|
else
|
|
text = L["Memory: %.0f KB"]:format(memory)
|
|
end
|
|
GameTooltip:AddLine(text)
|
|
end
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
end
|
|
|
|
if not wow_110 then
|
|
local InitButton = function(entry, addonIndex)
|
|
local checkbox = entry.Enabled
|
|
local title = entry.Title
|
|
local status = entry.Status
|
|
|
|
if BetterAddonListDB.hide_icons ~= false then -- false = show all
|
|
local titleIcon
|
|
local iconTexture = GetAddOnMetadata(addonIndex, "IconTexture")
|
|
local iconAtlas = GetAddOnMetadata(addonIndex, "IconAtlas")
|
|
if not iconTexture and not iconAtlas and BetterAddonListDB.hide_icons == nil then -- nil = nodefault
|
|
titleIcon = CreateSimpleTextureMarkup("Interface\\ICONS\\INV_Misc_QuestionMark", 20, 20)
|
|
elseif BetterAddonListDB.hide_icons then -- true = hide
|
|
if iconTexture then
|
|
titleIcon = CreateSimpleTextureMarkup(iconTexture, 20, 20)
|
|
elseif iconAtlas then
|
|
titleIcon = CreateAtlasMarkup(iconAtlas, 20, 20)
|
|
else
|
|
titleIcon = CreateSimpleTextureMarkup("Interface\\ICONS\\INV_Misc_QuestionMark", 20, 20)
|
|
end
|
|
end
|
|
if titleIcon then
|
|
local name = title:GetText()
|
|
local _, start = name:find(titleIcon, nil, true)
|
|
if start then
|
|
title:SetText(name:sub(start + 2))
|
|
end
|
|
end
|
|
end
|
|
|
|
local lockIcon = entry.Protected
|
|
if not lockIcon then
|
|
lockIcon = CreateFrame("Button", nil, entry, nil, addonIndex)
|
|
lockIcon:SetSize(16, 16)
|
|
lockIcon:SetPoint("CENTER", checkbox, "CENTER")
|
|
lockIcon:SetNormalTexture([[Interface\Glues\CharacterSelect\Glues-AddOn-Icons]])
|
|
lockIcon:GetNormalTexture():SetTexCoord(0, 16/64, 0, 1) -- AddonList_SetSecurityIcon
|
|
lockIcon:SetScript("OnClick", OnClick)
|
|
lockIcon:Hide()
|
|
entry.Protected = lockIcon
|
|
|
|
checkbox:HookScript("OnClick", function(self, ...) OnClick(lockIcon, ...) end)
|
|
end
|
|
|
|
local memIcon = entry.Memory
|
|
if not memIcon then
|
|
memIcon = CreateFrame("Button", nil, entry, nil, addonIndex)
|
|
memIcon:SetSize(6, 32)
|
|
memIcon:SetPoint("RIGHT", checkbox, "LEFT", 1, 0)
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem0]])
|
|
entry.Memory = memIcon
|
|
end
|
|
|
|
-- fix the "Load AddOn" text overflowing for some locales
|
|
local load = entry.LoadAddonButton
|
|
load:SetText(L.LOAD_ADDON)
|
|
load:SetWidth(#L.LOAD_ADDON > 12 and 120 or 100)
|
|
|
|
local enabled = C_AddOns.GetAddOnEnableState(addonIndex, character) > 0
|
|
|
|
-- XXX Handle enabled state stuff until Blizzard fixes their GetAddOnEnableState usage
|
|
local loadable, reason = C_AddOns.IsAddOnLoadable(addonIndex, character)
|
|
if loadable or (enabled and (reason == "DEP_DEMAND_LOADED" or reason == "DEMAND_LOADED")) then
|
|
title:SetTextColor(1.0, 0.78, 0.0)
|
|
elseif enabled and reason ~= "DEP_DISABLED" then
|
|
title:SetTextColor(1.0, 0.1, 0.1)
|
|
else
|
|
title:SetTextColor(0.5, 0.5, 0.5)
|
|
end
|
|
if enabled ~= AddonList.startStatus[addonIndex] and reason ~= "DEP_DISABLED" or
|
|
(reason ~= "INTERFACE_VERSION" and tContains(AddonList.outOfDateIndexes, addonIndex)) or
|
|
(reason == "INTERFACE_VERSION" and not tContains(AddonList.outOfDateIndexes, addonIndex))
|
|
then
|
|
if enabled then
|
|
-- special case for loadable on demand addons
|
|
if AddonList_IsAddOnLoadOnDemand(addonIndex) then
|
|
AddonList_SetStatus(entry, true, false, false)
|
|
else
|
|
AddonList_SetStatus(entry, false, false, true)
|
|
end
|
|
else
|
|
AddonList_SetStatus(entry, false, false, true)
|
|
end
|
|
else
|
|
AddonList_SetStatus(entry, false, true, false)
|
|
end
|
|
-- XXX
|
|
|
|
if enabled then
|
|
local depsEnabled = CheckAddonDependencies(C_AddOns.GetAddOnDependencies(addonIndex))
|
|
if not depsEnabled then
|
|
title:SetTextColor(1.0, 0.1, 0.1)
|
|
status:SetText(_G.ADDON_DEP_DISABLED)
|
|
end
|
|
if C_AddOns.IsAddOnLoadOnDemand(addonIndex) and not C_AddOns.IsAddOnLoaded(addonIndex) and depsEnabled then
|
|
AddonList_SetStatus(entry, true, false, false)
|
|
end
|
|
|
|
if not BetterAddonListDB.hide_memory then
|
|
local memory = GetAddOnMemoryUsage(addonIndex)
|
|
entry.memoryUsage = memory
|
|
local usage = memory / 8000 -- just needed some baseline
|
|
if usage > 0.8 then
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem5]])
|
|
elseif usage > 0.6 then
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem4]])
|
|
elseif usage > 0.4 then
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem3]])
|
|
elseif usage > 0.2 then
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem2]])
|
|
elseif usage > 0.1 then
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem1]])
|
|
else
|
|
memIcon:SetNormalTexture([[Interface\AddOns\BetterAddonList\textures\mem0]])
|
|
end
|
|
memIcon:Show()
|
|
else
|
|
entry.memoryUsage = nil
|
|
memIcon:Hide()
|
|
end
|
|
else
|
|
entry.memoryUsage = nil
|
|
memIcon:Hide()
|
|
end
|
|
|
|
local protected = IsAddonProtected(addonIndex)
|
|
lockIcon:SetShown(protected)
|
|
checkbox:SetShown(not protected)
|
|
end
|
|
hooksecurefunc("AddonList_InitButton", InitButton)
|
|
|
|
hooksecurefunc("AddonList_LoadAddOn", function(index)
|
|
UpdateAddOnMemoryUsage()
|
|
for _, frame in AddonList.ScrollBox:EnumerateFrames() do
|
|
if frame:GetID() == index then
|
|
InitButton(frame, index)
|
|
return
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- search / filter
|
|
do
|
|
AddonList.filterList = {}
|
|
local filterList = AddonList.filterList
|
|
|
|
local filters = { -- for menu order
|
|
"ENABLED",
|
|
"DISABLED",
|
|
"LOD",
|
|
"PROTECTED",
|
|
}
|
|
|
|
local filterFunc = {
|
|
ENABLED = function(index) return C_AddOns.GetAddOnEnableState(index, character) > 0 end,
|
|
DISABLED = function(index) return C_AddOns.GetAddOnEnableState(index, character) == 0 end,
|
|
LOD = function(index) return C_AddOns.IsAddOnLoadOnDemand(index) end,
|
|
PROTECTED = function(index) return IsAddonProtected(index) end,
|
|
}
|
|
local function checkFilters(index)
|
|
for filter, value in next, filterList do
|
|
if value and not filterFunc[filter](index) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local fullList = CreateIndexRangeDataProvider(C_AddOns.GetNumAddOns())
|
|
local searchList = CreateDataProvider()
|
|
AddonList.searchList = searchList
|
|
|
|
local strfind = string.find
|
|
local searchString = ""
|
|
local function OnTextChanged(self)
|
|
if wow_110 then return end
|
|
|
|
SearchBoxTemplate_OnTextChanged(self)
|
|
local oldText = searchString
|
|
searchString = self:GetText():lower():trim()
|
|
|
|
searchList:Flush()
|
|
if (searchString ~= "" and oldText ~= searchString) or next(filterList) then
|
|
local list = {}
|
|
for i=1, C_AddOns.GetNumAddOns() do
|
|
local name, title = C_AddOns.GetAddOnInfo(i)
|
|
if (searchString == "" or (strfind(name:lower(), searchString, nil, true) or (title and strfind(title:lower(), searchString, nil, true)))) and checkFilters(i) then
|
|
list[#list + 1] = i
|
|
end
|
|
end
|
|
if #list > 0 then
|
|
searchList:InsertTable(list)
|
|
end
|
|
end
|
|
UpdateList()
|
|
end
|
|
|
|
-- XXX Blizzard uses guid for EnableAddOn/DisableAddOn, but name for GetAddOnEnableState.
|
|
-- This apparently isn't mapped to the same state table internally so the addon list via
|
|
-- AddonList_HasAnyChanged doesn't update and show the reload button properly?
|
|
local function HasAnyChanged()
|
|
local checkIfOutOfDate = false
|
|
if AddonList.outOfDate and not C_AddOns.IsAddonVersionCheckEnabled() then
|
|
return true
|
|
elseif not AddonList.outOfDate and C_AddOns.IsAddonVersionCheckEnabled() then
|
|
checkIfOutOfDate = true
|
|
end
|
|
|
|
for i = 1, C_AddOns.GetNumAddOns() do
|
|
local enabled = C_AddOns.GetAddOnEnableState(i, character) > 0
|
|
local _, _, _, loadable, reason = C_AddOns.GetAddOnInfo(i)
|
|
if checkIfOutOfDate and enabled and not loadable and reason == "INTERFACE_VERSION" then
|
|
return true
|
|
end
|
|
if enabled ~= AddonList.startStatus[i] and reason ~= "DEP_DISABLED" then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function UpdateList()
|
|
if wow_110 then
|
|
AddonList_Update()
|
|
return
|
|
else
|
|
if not AddonList.searchList:IsEmpty() then
|
|
AddonList.ScrollBox:SetDataProvider(AddonList.searchList, true)
|
|
else
|
|
AddonList.ScrollBox:SetDataProvider(fullList, true)
|
|
end
|
|
end
|
|
|
|
local button = AddonListOkayButton or AddonList.OkayButton
|
|
if HasAnyChanged() then
|
|
button:SetText(_G.RELOADUI)
|
|
AddonList.shouldReload = true
|
|
else
|
|
button:SetText(_G.OKAY)
|
|
AddonList.shouldReload = false
|
|
end
|
|
end
|
|
|
|
hooksecurefunc("AddonList_Update", function()
|
|
if not wow_110 and not AddonList.searchList:IsEmpty() then
|
|
AddonList.ScrollBox:SetDataProvider(AddonList.searchList, true)
|
|
end
|
|
|
|
local button = AddonListOkayButton or AddonList.OkayButton
|
|
if HasAnyChanged() then
|
|
button:SetText(_G.RELOADUI)
|
|
AddonList.shouldReload = true
|
|
else
|
|
button:SetText(_G.OKAY)
|
|
AddonList.shouldReload = false
|
|
end
|
|
end)
|
|
|
|
AddonList:HookScript("OnHide", function(self)
|
|
-- reset search box
|
|
wipe(self.filterList)
|
|
self.SearchBox:SetText("")
|
|
OnTextChanged(self.SearchBox)
|
|
end)
|
|
|
|
if not wow_110 then
|
|
local editBox = CreateFrame("EditBox", "BetterAddonListSearchBox", AddonList, "SearchBoxTemplate")
|
|
editBox:SetPoint("TOPRIGHT", -107, -33) -- -107 w/filter, -11 w/o
|
|
editBox:SetSize(115, 20)
|
|
editBox:SetMaxLetters(40)
|
|
editBox:SetScript("OnTextChanged", OnTextChanged)
|
|
AddonList.SearchBox = editBox
|
|
|
|
local filterButton = CreateFrame("DropdownButton", "BetterAddonListFilterButton", AddonList, "WowStyle1FilterDropdownTemplate")
|
|
filterButton:SetPoint("LEFT", editBox, "RIGHT", 3, 0)
|
|
filterButton:SetSize(93, 22)
|
|
|
|
local function isFilterSelected(key)
|
|
return filterList[key]
|
|
end
|
|
local function setFilterSelected(key)
|
|
filterList[key] = not filterList[key]
|
|
OnTextChanged(editBox)
|
|
end
|
|
filterButton:SetupMenu(function(_, root)
|
|
for _, key in ipairs(filters) do
|
|
root:CreateCheckbox(L[("FILTER_%s"):format(key)], isFilterSelected, setFilterSelected, key)
|
|
end
|
|
end)
|
|
|
|
filterButton:SetDefaultCallback(function()
|
|
wipe(filterList)
|
|
OnTextChanged(editBox)
|
|
end)
|
|
filterButton:SetIsDefaultCallback(function()
|
|
for _, value in next, filterList do
|
|
if value then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end)
|
|
end
|
|
end
|
|
|
|
function addon:EnableProtected()
|
|
C_AddOns.EnableAddOn(ADDON_NAME)
|
|
for name in next, BetterAddonListDB.protected do
|
|
C_AddOns.EnableAddOn(name)
|
|
end
|
|
end
|
|
|
|
function addon:LoadSet(name)
|
|
C_AddOns.DisableAllAddOns(character)
|
|
self:EnableSet(name)
|
|
end
|
|
|
|
function addon:EnableSet(name, done)
|
|
local set = sets[name]
|
|
if set and #set > 0 then
|
|
for i=1, C_AddOns.GetNumAddOns() do
|
|
local addon_name = C_AddOns.GetAddOnInfo(i)
|
|
if tContains(set, addon_name) then
|
|
C_AddOns.EnableAddOn(i, character)
|
|
end
|
|
end
|
|
end
|
|
if included[name] then
|
|
done = done or { [name] = true }
|
|
for included_set in next, included[name] do
|
|
if not done[included_set] then
|
|
done[included_set] = true
|
|
self:EnableSet(included_set, done)
|
|
end
|
|
end
|
|
end
|
|
UpdateList()
|
|
end
|
|
|
|
function addon:DisableSet(name)
|
|
local set = sets[name]
|
|
if set and #set > 0 then
|
|
for i=1, C_AddOns.GetNumAddOns() do
|
|
local addon_name = C_AddOns.GetAddOnInfo(i)
|
|
if not IsAddonProtected(i) and tContains(set, addon_name) then
|
|
C_AddOns.DisableAddOn(i, character)
|
|
end
|
|
end
|
|
end
|
|
UpdateList()
|
|
end
|
|
|
|
function addon:SaveSet(name)
|
|
if not name or name == "" then return end
|
|
|
|
local set = sets[name]
|
|
if not set then
|
|
sets[name] = {}
|
|
set = sets[name]
|
|
end
|
|
wipe(set)
|
|
|
|
for i=1, C_AddOns.GetNumAddOns() do
|
|
local enabled = C_AddOns.GetAddOnEnableState(i, character) > 0
|
|
if enabled and not IsAddonProtected(i) then
|
|
set[#set+1] = C_AddOns.GetAddOnInfo(i)
|
|
end
|
|
end
|
|
end
|
|
|
|
function addon:RenameSet(name, newName)
|
|
if not sets[name] then return end
|
|
if sets[newName] then return end
|
|
|
|
sets[newName] = CopyTable(sets[name])
|
|
sets[name] = nil
|
|
end
|
|
|
|
function addon:DeleteSet(name)
|
|
if not sets[name] then return end
|
|
|
|
sets[name] = nil
|
|
end
|
|
|