if not WeakAuras.IsCorrectVersion() then return end -- Lua APIs local tinsert, tremove, wipe = table.insert, table.remove, wipe local fmt, tostring = string.format, tostring local pairs, type, unpack = pairs, type, unpack local loadstring, error = loadstring, error local coroutine = coroutine local _G = _G -- WoW APIs local InCombatLockdown = InCombatLockdown local GetSpellInfo, GetItemInfo, GetItemIcon, UnitName = GetSpellInfo, GetItemInfo, GetItemIcon, UnitName local GetScreenWidth, GetScreenHeight, GetBuildInfo, GetLocale, GetTime, CreateFrame, IsAddOnLoaded, LoadAddOn = GetScreenWidth, GetScreenHeight, GetBuildInfo, GetLocale, GetTime, CreateFrame, IsAddOnLoaded, LoadAddOn local AceGUI = LibStub("AceGUI-3.0") local WeakAuras = WeakAuras local L = WeakAuras.L local ADDON_NAME = "WeakAurasOptions"; local prettyPrint = WeakAuras.prettyPrint local ValidateNumeric = WeakAuras.ValidateNumeric; local dynFrame = WeakAuras.dynFrame; WeakAuras.transmitCache = {}; local regionOptions = WeakAuras.regionOptions; local displayButtons = {}; WeakAuras.displayButtons = displayButtons; local optionReloads = {}; local optionTriggerChoices = {}; WeakAuras.optionTriggerChoices = optionTriggerChoices; local displayOptions = {}; WeakAuras.displayOptions = displayOptions; local loaded = WeakAuras.loaded; local spellCache = WeakAuras.spellCache; local savedVars = {}; WeakAuras.savedVars = savedVars; local tempGroup = { id = {"tempGroup"}, regionType = "group", controlledChildren = {}, load = {}, triggers = {{}}, config = {}, authorOptions = {}, anchorPoint = "CENTER", anchorFrameType = "SCREEN", xOffset = 0, yOffset = 0 }; WeakAuras.tempGroup = tempGroup; function WeakAuras.DuplicateAura(data, newParent) local base_id = data.id .. " " local num = 2 -- if the old id ends with a number increment the number local matchName, matchNumber = string.match(data.id, "^(.-)(%d*)$") matchNumber = tonumber(matchNumber) if (matchName ~= "" and matchNumber ~= nil) then base_id = matchName num = matchNumber + 1 end local new_id = base_id .. num while(WeakAuras.GetData(new_id)) do new_id = base_id .. num num = num + 1 end local newData = {} WeakAuras.DeepCopy(data, newData) newData.id = new_id newData.parent = nil newData.uid = WeakAuras.GenerateUniqueID() if newData.controlledChildren then newData.controlledChildren = {} end WeakAuras.Add(newData) WeakAuras.NewDisplayButton(newData) if(newParent or data.parent) then local parentId = newParent or data.parent local parentData = WeakAuras.GetData(parentId) local index if newParent then index = #parentData.controlledChildren else index = tIndexOf(parentData.controlledChildren, data.id) end if(index) then tinsert(parentData.controlledChildren, index + 1, newData.id) newData.parent = parentId WeakAuras.Add(parentData) WeakAuras.Add(newData) for index, id in pairs(parentData.controlledChildren) do local childButton = WeakAuras.GetDisplayButton(id) childButton:SetGroup(parentData.id, parentData.regionType == "dynamicgroup") childButton:SetGroupOrder(index, #parentData.controlledChildren) end local button = WeakAuras.GetDisplayButton(parentData.id) button.callbacks.UpdateExpandButton() WeakAuras.UpdateDisplayButton(parentData) WeakAuras.ReloadGroupRegionOptions(parentData) end end return newData.id end local point_types = WeakAuras.point_types; local operator_types = WeakAuras.operator_types; local operator_types_without_equal = WeakAuras.operator_types_without_equal; local string_operator_types = WeakAuras.string_operator_types; local eventend_types = WeakAuras.eventend_types; local autoeventend_types = WeakAuras.autoeventend_types; local timedeventend_types = WeakAuras.timedeventend_types; local subevent_actual_prefix_types = WeakAuras.subevent_actual_prefix_types; local function union(table1, table2) local meta = {}; for i,v in pairs(table1) do meta[i] = v; end for i,v in pairs(table2) do meta[i] = v; end return meta; end AceGUI:RegisterLayout("AbsoluteList", function(content, children) local yOffset = 0; for i = 1, #children do local child = children[i] local frame = child.frame; frame:ClearAllPoints(); frame:Show(); frame:SetPoint("LEFT", content); frame:SetPoint("RIGHT", content); frame:SetPoint("TOP", content, "TOP", 0, yOffset) if child.DoLayout then child:DoLayout() end yOffset = yOffset - ((frame.height or frame:GetHeight() or 0) + 2); end if(content.obj.LayoutFinished) then content.obj:LayoutFinished(nil, yOffset * -1); end end); AceGUI:RegisterLayout("ButtonsScrollLayout", function(content, children, skipLayoutFinished) local yOffset = 0 local scrollTop, scrollBottom = content.obj:GetScrollPos() for i = 1, #children do local child = children[i] local frame = child.frame; if not child.dragging then local frameHeight = (frame.height or frame:GetHeight() or 0); frame:ClearAllPoints(); if (-yOffset + frameHeight > scrollTop and -yOffset - frameHeight < scrollBottom) then frame:Show(); frame:SetPoint("LEFT", content); frame:SetPoint("RIGHT", content); frame:SetPoint("TOP", content, "TOP", 0, yOffset) else frame:Hide(); frame.yOffset = yOffset end yOffset = yOffset - (frameHeight + 2); end if child.DoLayout then child:DoLayout() end end if(content.obj.LayoutFinished and not skipLayoutFinished) then content.obj:LayoutFinished(nil, yOffset * -1) end end) function WeakAuras.MultipleDisplayTooltipDesc() local desc = {{L["Multiple Displays"], L["Temporary Group"]}}; for index, id in pairs(tempGroup.controlledChildren) do desc[index + 1] = {" ", id}; end desc[2][1] = L["Children:"] tinsert(desc, " "); tinsert(desc, {" ", "|cFF00FFFF"..L["Right-click for more options"]}); tinsert(desc, {" ", "|cFF00FFFF"..L["Drag to move"]}); return desc; end function WeakAuras.ConstructOptions(prototype, data, startorder, triggernum, triggertype, unevent) local trigger, untrigger; if(data.controlledChildren) then trigger, untrigger = {}, {}; elseif(triggertype == "load") then trigger = data.load; elseif data.triggers[triggernum] then if(triggertype == "untrigger") then trigger = data.triggers[triggernum].untrigger else trigger, untrigger = data.triggers[triggernum].trigger, data.triggers[triggernum].untrigger end else error("Improper argument to WeakAuras.ConstructOptions - trigger number not in range"); end unevent = unevent or trigger.unevent; local options = {}; local order = startorder or 10; local isCollapsedFunctions; for index, arg in pairs(prototype.args) do local hidden = nil; if(arg.collapse and isCollapsedFunctions[arg.collapse] and type(arg.enable) == "function") then local isCollapsed = isCollapsedFunctions[arg.collapse] hidden = function() return isCollapsed() or not arg.enable(trigger) end elseif(type(arg.enable) == "function") then hidden = function() return not arg.enable(trigger) end; elseif(arg.collapse and isCollapsedFunctions[arg.collapse]) then hidden = isCollapsedFunctions[arg.collapse] end local name = arg.name; local validate = arg.validate; local reloadOptions = arg.reloadOptions; if (name and arg.type == "collapse") then options["summary_" .. arg.name] = { type = "execute", control = "WeakAurasExpandSmall", width = WeakAuras.doubleWidth, name = type(arg.display) == "function" and arg.display(trigger) or arg.display, order = order, image = function() local collapsed = WeakAuras.IsCollapsed("trigger", name, "", true) return collapsed and "Interface\\AddOns\\WeakAuras\\Media\\Textures\\edit" or "Interface\\AddOns\\WeakAuras\\Media\\Textures\\editdown" end, imageWidth = 24, imageHeight = 24, func = function() local collapsed = WeakAuras.IsCollapsed("trigger", name, "", true) WeakAuras.SetCollapsed("trigger", name, "", not collapsed) WeakAuras.ReloadTriggerOptions(data); end, } order = order + 1; isCollapsedFunctions = isCollapsedFunctions or {}; isCollapsedFunctions[name] = function() return WeakAuras.IsCollapsed("trigger", name, "", true); end elseif(name and not arg.hidden) then local realname = name; if(triggertype == "untrigger") then name = "untrigger_"..name; end if (arg.type == "multiselect") then -- Ensure new line for non-toggle options options["spacer_"..name] = { type = "description", width = WeakAuras.doubleWidth, name = "", order = order, hidden = hidden, } order = order + 1; end if(arg.type == "tristate" or arg.type == "tristatestring") then options["use_"..name] = { type = "toggle", width = WeakAuras.normalWidth, name = function(input) local value = trigger["use_"..realname]; if(value == nil) then return arg.display; elseif(value == false) then return "|cFFFF0000 "..L["Negator"].." "..arg.display; else return "|cFF00FF00"..arg.display; end end, desc = arg.desc, get = function() local value = trigger["use_"..realname]; if(value == nil) then return false; elseif(value == false) then return "false"; else return "true"; end end, set = function(info, v) if(v) then trigger["use_"..realname] = true; else local value = trigger["use_"..realname]; if(value == false) then trigger["use_"..realname] = nil; else trigger["use_"..realname] = false end end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end, hidden = hidden, order = order }; elseif(arg.type == "multiselect") then options["use_"..name] = { type = "toggle", width = WeakAuras.normalWidth, name = arg.display, desc = function() local v = trigger["use_"..realname]; if(v == true) then return L["Multiselect single tooltip"]; elseif(v == false) then return L["Multiselect multiple tooltip"]; else return L["Multiselect ignored tooltip"]; end end, get = function() local value = trigger["use_"..realname]; if(value == nil) then return false; elseif(value == false) then return "false"; else return "true"; end end, set = function(info, v) if(v) then trigger["use_"..realname] = true; else local value = trigger["use_"..realname]; if(value == false) then trigger["use_"..realname] = nil; else trigger["use_"..realname] = false trigger[realname] = trigger[realname] or {}; if(trigger[realname].single) then trigger[realname].multi = trigger[realname].multi or {}; trigger[realname].multi[trigger[realname].single] = true; end end end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end, hidden = hidden, order = order }; elseif (arg.type == "description") then options["description_space_"..name] = { type = "description", width = WeakAuras.doubleWidth, name = "", order = order, hidden = hidden, } options["description_title_"..name] = { type = "description", width = WeakAuras.doubleWidth, name = arg.display, order = order, hidden = hidden, fontSize = "large", } order = order + 1; options["description_"..name] = { type = "description", width = WeakAuras.doubleWidth, name = arg.text, order = order, hidden = hidden, } order = order + 1; else options["use_"..name] = { type = "toggle", width = arg.width or WeakAuras.normalWidth, name = arg.display, order = order, hidden = hidden, desc = arg.desc, get = function() return trigger["use_"..realname]; end, set = function(info, v) trigger["use_"..realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; end if(arg.type == "toggle" or arg.type == "tristate") then options["use_"..name].width = arg.width or WeakAuras.doubleWidth; end if(arg.type == "toggle") then options["use_"..name].desc = arg.desc; end if(arg.required) then trigger["use_"..realname] = true; if not(triggertype) then options["use_"..name].disabled = true; else options["use_"..name] = nil; order = order - 1; end end order = order + 1; if(arg.type == "number") then if (not arg.noOperator) then options[name.."_operator"] = { type = "select", width = WeakAuras.halfWidth, name = L["Operator"], order = order, hidden = hidden, values = arg.operator_types_without_equal and operator_types_without_equal or operator_types, disabled = function() return not trigger["use_"..realname]; end, get = function() return trigger["use_"..realname] and trigger[realname.."_operator"] or nil; end, set = function(info, v) trigger[realname.."_operator"] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name.."_operator"].set = function(info, v) trigger[realname.."_operator"] = v; untrigger[realname.."_operator"] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.SortDisplayButtons(); end elseif(arg.required and triggertype == "untrigger") then options[name.."_operator"] = nil; order = order - 1; end order = order + 1; end options[name] = { type = "input", width = arg.noOperator and WeakAuras.normalWidth or WeakAuras.halfWidth, validate = ValidateNumeric, name = arg.display, order = order, hidden = hidden, desc = arg.desc, disabled = function() return not trigger["use_"..realname]; end, get = function() return trigger["use_"..realname] and trigger[realname] or nil; end, set = function(info, v) trigger[realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name].set = function(info, v) trigger[realname] = v; untrigger[realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.SortDisplayButtons(); end elseif(arg.required and triggertype == "untrigger") then options[name] = nil; order = order - 1; end order = order + 1; elseif(arg.type == "string" or arg.type == "tristatestring") then options[name] = { type = "input", width = WeakAuras.normalWidth, name = arg.display, order = order, hidden = hidden, validate = validate, desc = arg.desc, set = function(info, v) trigger[realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if arg.type == "string" then options[name].disabled = function() return not trigger["use_"..realname] end options[name].get = function() return trigger["use_"..realname] and trigger[realname] or nil; end else options[name].disabled = function() return trigger["use_"..realname] == nil end options[name].get = function() return trigger["use_"..realname] ~= nil and trigger[realname] or nil; end end if(arg.required and not triggertype) then options[name].set = function(info, v) trigger[realname] = v; untrigger[realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.SortDisplayButtons(); end elseif(arg.required and triggertype == "untrigger") then options[name] = nil; order = order - 1; end order = order + 1; elseif(arg.type == "longstring") then options[name.."_operator"] = { type = "select", width = WeakAuras.normalWidth, name = L["Operator"], order = order, hidden = hidden, values = string_operator_types, disabled = function() return not trigger["use_"..realname]; end, get = function() return trigger["use_"..realname] and trigger[realname.."_operator"] or nil; end, set = function(info, v) trigger[realname.."_operator"] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name.."_operator"].set = function(info, v) trigger[realname.."_operator"] = v; untrigger[realname.."_operator"] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.SortDisplayButtons(); end elseif(arg.required and triggertype == "untrigger") then options[name.."_operator"] = nil; order = order - 1; end order = order + 1; options[name] = { type = "input", width = WeakAuras.doubleWidth, name = arg.display, order = order, hidden = hidden, validate = validate, disabled = function() return not trigger["use_"..realname]; end, get = function() return trigger["use_"..realname] and trigger[realname] or nil; end, set = function(info, v) trigger[realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name].set = function(info, v) trigger[realname] = v; untrigger[realname] = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.SortDisplayButtons(); end elseif(arg.required and triggertype == "untrigger") then options[name] = nil; order = order - 1; end order = order + 1; elseif(arg.type == "spell" or arg.type == "aura" or arg.type == "item") then if(not arg.required or triggertype ~= "untrigger") then if (arg.showExactOption) then options["exact"..name] = { type = "toggle", width = WeakAuras.normalWidth - 0.1, name = L["Exact Spell Match"], order = order, hidden = hidden, get = function() return trigger["use_exact_"..realname]; end, set = function(info, v) trigger["use_exact_"..realname] = v; WeakAuras.Add(data); WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end, }; order = order + 1; end options["icon"..name] = { type = "execute", width = 0.1, name = "", order = order, hidden = hidden, image = function() if(trigger["use_"..realname] and trigger[realname]) then if(arg.type == "aura") then local icon = spellCache.GetIcon(trigger[realname]); return icon and tostring(icon) or "", 18, 18; elseif(arg.type == "spell") then local _, _, icon = GetSpellInfo(trigger[realname] or 0); return icon and tostring(icon) or "", 18, 18; elseif(arg.type == "item") then local _, _, _, _, _, _, _, _, _, icon = GetItemInfo(trigger[realname]); return icon and tostring(icon) or "", 18, 18; end else return "", 18, 18; end end, disabled = function() return not ((arg.type == "aura" and trigger[realname] and spellCache.GetIcon(trigger[realname])) or (arg.type == "spell" and trigger[realname] and GetSpellInfo(trigger[realname] or 0)) or (arg.type == "item" and trigger[realname] and GetItemIcon(trigger[realname]))) end }; order = order + 1; options[name] = { type = "input", width = WeakAuras.doubleWidth, name = arg.display, order = order, hidden = hidden, validate = validate, disabled = function() return not trigger["use_"..realname]; end, get = function() if(arg.type == "item") then if(trigger["use_"..realname] and trigger[realname] and trigger[realname] ~= "") then local name = GetItemInfo(trigger[realname]); if(name) then return name; else local itemId = tonumber(trigger[realname]) if itemId and itemId ~= 0 then return tostring(trigger[realname]) end return L["Invalid Item Name/ID/Link"]; end else return nil; end elseif(arg.type == "spell") then local useExactSpellId = (arg.showExactOption and trigger["use_exact_"..realname]) or arg.forceExactOption if(trigger["use_"..realname]) then if (trigger[realname] and trigger[realname] ~= "") then if useExactSpellId then local spellId = tonumber(trigger[realname]) if (spellId and spellId ~= 0) then return tostring(spellId); end else local name = GetSpellInfo(trigger[realname] or 0); if(name) then return name; end end end return useExactSpellId and L["Invalid Spell ID"] or (arg.conditionType == "string" and L["Invalid Spell Name/Link"] or L["Invalid Spell Name/ID/Link"]); else return nil; end else return trigger["use_"..realname] and trigger[realname] or nil; end end, set = function(info, v) local fixedInput = v; if(arg.type == "aura") then fixedInput = WeakAuras.spellCache.CorrectAuraName(v); elseif(arg.type == "spell") then fixedInput = WeakAuras.CorrectSpellName(v); elseif(arg.type == "item") then fixedInput = WeakAuras.CorrectItemName(v); end trigger[realname] = fixedInput; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; order = order + 1; end elseif(arg.type == "select" or arg.type == "unit") then local values; if(type(arg.values) == "function") then values = arg.values(trigger); else values = WeakAuras[arg.values]; end options[name] = { type = "select", width = WeakAuras.normalWidth, name = arg.display, order = order, hidden = hidden, values = values, disabled = function() return not trigger["use_"..realname]; end, get = function() if(arg.type == "unit" and trigger["use_specific_"..realname]) then return "member"; end if (not trigger["use_"..realname]) then return nil; end if (arg.default and (not trigger[realname] or not values[trigger[realname]])) then trigger[realname] = arg.default; return arg.default; end return trigger[realname] or nil; end, set = function(info, v) trigger[realname] = v; if(arg.type == "unit" and v == "member") then trigger["use_specific_"..realname] = true; trigger[realname] = UnitName("player"); else trigger["use_specific_"..realname] = nil; end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name].set = function(info, v) trigger[realname] = v; if(arg.type == "unit" and v == "member") then trigger["use_specific_"..realname] = true; else trigger["use_specific_"..realname] = nil; end untrigger[realname] = v; if(arg.type == "unit" and v == "member") then untrigger["use_specific_"..realname] = true; else untrigger["use_specific_"..realname] = nil; end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end elseif(arg.required and triggertype == "untrigger") then options[name] = nil; order = order - 1; end if (arg.control) then options[name].control = arg.control; end order = order + 1; if(arg.type == "unit" and not (arg.required and triggertype == "untrigger")) then options["use_specific_"..name] = { type = "toggle", width = WeakAuras.normalWidth, name = L["Specific Unit"], order = order, hidden = function() return (not trigger["use_specific_"..realname] and trigger[realname] ~= "member") or (type(hidden) == "function" and hidden(trigger)) or (type(hidden) ~= "function" and hidden) end, get = function() return true end, set = function(info, v) trigger["use_specific_"..realname] = nil; options[name].set(info, "player"); end } order = order + 1; options["specific_"..name] = { type = "input", width = WeakAuras.normalWidth, name = L["Specific Unit"], desc = L["Can be a UID (e.g., party1)."], order = order, hidden = function() return (not trigger["use_specific_"..realname] and trigger[realname] ~= "member") or (type(hidden) == "function" and hidden(trigger)) or (type(hidden) ~= "function" and hidden) end, get = function() return trigger[realname] end, set = function(info, v) trigger[realname] = v; if(arg.required and not triggertype) then untrigger[realname] = v; end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end end }; order = order + 1; end elseif(arg.type == "multiselect") then local values; if(type(arg.values) == "function") then values = arg.values(trigger); else values = WeakAuras[arg.values]; end options[name] = { type = "select", width = WeakAuras.normalWidth, name = arg.display, order = order, values = values, control = arg.control, hidden = function() return (type(hidden) == "function" and hidden(trigger)) or (type(hidden) ~= "function" and hidden) or trigger["use_"..realname] == false; end, disabled = function() return not trigger["use_"..realname]; end, get = function() return trigger["use_"..realname] and trigger[realname] and trigger[realname].single or nil; end, set = function(info, v) trigger[realname] = trigger[realname] or {}; trigger[realname].single = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name].set = function(info, v) trigger[realname].single = v; untrigger[realname].single = v; WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end end options["multiselect_"..name] = { type = "multiselect", name = arg.display, width = WeakAuras.doubleWidth, order = order, hidden = function() return (type(hidden) == "function" and hidden(trigger)) or (type(hidden) ~= "function" and hidden) or trigger["use_"..realname] ~= false; end, values = values, get = function(info, v) if(trigger["use_"..realname] == false and trigger[realname] and trigger[realname].multi) then return trigger[realname].multi[v]; end end, set = function(info, v, calledFromSetAll) trigger[realname].multi = trigger[realname].multi or {}; if (calledFromSetAll) then trigger[realname].multi[v] = calledFromSetAll; elseif(trigger[realname].multi[v]) then trigger[realname].multi[v] = nil; else trigger[realname].multi[v] = true; end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end }; if(arg.required and not triggertype) then options[name].set = function(info, v) if(trigger[realname].multi[v]) then trigger[realname].multi[v] = nil; else trigger[realname].multi[v] = true; end if(untrigger[realname].multi[v]) then untrigger[realname].multi[v] = nil; else untrigger[realname].multi[v] = true; end WeakAuras.Add(data); if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.SortDisplayButtons(); end end if(arg.required and triggertype == "untrigger") then options[name] = nil; options["multiselect_"..name] = nil; else order = order + 1; end end end end if not(triggertype or prototype.automaticrequired) then options.unevent = { type = "select", width = WeakAuras.doubleWidth, name = L["Hide"], order = order }; order = order + 1; if(unevent == "timed") then options.unevent.width = WeakAuras.normalWidth; options.duration = { type = "input", width = WeakAuras.normalWidth, name = L["Duration (s)"], order = order } order = order + 1; else options.unevent.width = WeakAuras.doubleWidth; end if(unevent == "custom") then local unevent_options = WeakAuras.ConstructOptions(prototype, data, order, triggernum, "untrigger"); options = union(options, unevent_options); end if (prototype.timedrequired) then if (type(prototype.timedrequired) == "function") then local func = prototype.timedrequired options.unevent.values = function() if func(trigger) then return timedeventend_types else return eventend_types end end else options.unevent.values = timedeventend_types; end elseif (prototype.automatic) then options.unevent.values = autoeventend_types; else options.unevent.values = eventend_types; end end WeakAuras.option = options; return options; end local frame; local db; local odb; local options; local newOptions; local loadedOptions; local unloadedOptions; local reopenAfterCombat = false; local loadedFrame = CreateFrame("FRAME"); loadedFrame:RegisterEvent("ADDON_LOADED"); loadedFrame:RegisterEvent("PLAYER_REGEN_ENABLED"); loadedFrame:RegisterEvent("PLAYER_REGEN_DISABLED"); loadedFrame:SetScript("OnEvent", function(self, event, addon) if (event == "ADDON_LOADED") then if(addon == ADDON_NAME) then db = WeakAurasSaved; WeakAurasOptionsSaved = WeakAurasOptionsSaved or {}; odb = WeakAurasOptionsSaved; -- Remove icon and id cache (replaced with spellCache) if (odb.iconCache) then odb.iconCache = nil; end if (odb.idCache) then odb.idCache = nil; end odb.spellCache = odb.spellCache or {}; spellCache.Load(odb.spellCache); local _, build = GetBuildInfo(); local locale = GetLocale(); local version = WeakAuras.versionString local num = 0; for i,v in pairs(odb.spellCache) do num = num + 1; end if(num < 39000 or odb.locale ~= locale or odb.build ~= build or odb.version ~= version) then spellCache.Build(); odb.build = build; odb.locale = locale; odb.version = version; end -- Updates the icon cache with whatever icons WeakAuras core has actually used. -- This helps keep name<->icon matches relevant. for name, icons in pairs(db.dynamicIconCache) do if db.dynamicIconCache[name] then for spellId, icon in pairs(db.dynamicIconCache[name]) do spellCache.AddIcon(name, spellId, icon) end end end if odb.magnetAlign == nil then odb.magnetAlign = true end if db.import_disabled then db.import_disabled = nil end savedVars.db = db; savedVars.odb = odb; end elseif (event == "PLAYER_REGEN_DISABLED") then if(frame and frame:IsVisible()) then reopenAfterCombat = true; WeakAuras.HideOptions(); end elseif (event == "PLAYER_REGEN_ENABLED") then if (reopenAfterCombat) then reopenAfterCombat = nil; WeakAuras.ShowOptions() end end end); function WeakAuras.MultipleDisplayTooltipMenu() local frame = frame; local menu = { { text = L["Add to new Group"], notCheckable = 1, func = function() local data = { id = WeakAuras.FindUnusedId(tempGroup.controlledChildren[1].." Group"), regionType = "group", }; WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); for index, childId in pairs(tempGroup.controlledChildren) do local childData = WeakAuras.GetData(childId); tinsert(data.controlledChildren, childId); childData.parent = data.id; WeakAuras.Add(data); WeakAuras.Add(childData); end for index, id in pairs(data.controlledChildren) do local childButton = WeakAuras.GetDisplayButton(id); childButton:SetGroup(data.id, data.regionType == "dynamicgroup"); childButton:SetGroupOrder(index, #data.controlledChildren); end local button = WeakAuras.GetDisplayButton(data.id); button.callbacks.UpdateExpandButton(); WeakAuras.UpdateDisplayButton(data); WeakAuras.ReloadGroupRegionOptions(data); WeakAuras.SortDisplayButtons(); button:Expand(); end }, { text = L["Add to new Dynamic Group"], notCheckable = 1, func = function() local data = { id = WeakAuras.FindUnusedId(tempGroup.controlledChildren[1].." Group"), regionType = "dynamicgroup", }; WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); for index, childId in pairs(tempGroup.controlledChildren) do local childData = WeakAuras.GetData(childId); tinsert(data.controlledChildren, childId); childData.parent = data.id; childData.xOffset = 0; childData.yOffset = 0; WeakAuras.Add(data); WeakAuras.Add(childData); end for index, id in pairs(data.controlledChildren) do local childButton = WeakAuras.GetDisplayButton(id); childButton:SetGroup(data.id, data.regionType == "dynamicgroup"); childButton:SetGroupOrder(index, #data.controlledChildren); end local button = WeakAuras.GetDisplayButton(data.id); button.callbacks.UpdateExpandButton(); WeakAuras.UpdateDisplayButton(data); WeakAuras.ReloadGroupRegionOptions(data); WeakAuras.SortDisplayButtons(); button:Expand(); WeakAuras.PickDisplay(data.id); end }, { text = L["Duplicate All"], notCheckable = 1, func = function() local toDuplicate = {}; for index, id in pairs(tempGroup.controlledChildren) do toDuplicate[index] = id; end local duplicated = {}; for index, id in ipairs(toDuplicate) do local childData = WeakAuras.GetData(id); duplicated[index] = WeakAuras.DuplicateAura(childData); end WeakAuras.ClearPicks(); frame:PickDisplayBatch(duplicated); end }, { text = " ", notCheckable = 1, notClickable = 1 }, { text = L["Delete all"], notCheckable = 1, func = function() local toDelete = {}; local parents = {}; for index, id in pairs(tempGroup.controlledChildren) do local childData = WeakAuras.GetData(id); toDelete[index] = childData; if(childData.parent) then parents[childData.parent] = true; end end WeakAuras.ConfirmDelete(toDelete, parents) end }, { text = " ", notClickable = 1, notCheckable = 1, }, { text = L["Close"], notCheckable = 1, func = function() WeakAuras_DropDownMenu:Hide() end } }; local anyGrouped = false; for index, id in pairs(tempGroup.controlledChildren) do local childData = WeakAuras.GetData(id); if(childData and childData.parent) then anyGrouped = true; break; end end if(anyGrouped) then menu[1].notClickable = 1; menu[1].text = "|cFF777777"..menu[1].text; menu[2].notClickable = 1; menu[2].text = "|cFF777777"..menu[2].text; end return menu; end function WeakAuras.DeleteOption(data, massDelete) local id = data.id; local parentData; if(data.parent) then parentData = db.displays[data.parent]; end if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childButton = displayButtons[childId]; if(childButton) then childButton:SetGroup(); end local childData = db.displays[childId]; if(childData) then childData.parent = nil; end end end WeakAuras.CollapseAllClones(id); frame:ClearPicks(); WeakAuras.Delete(data); if(displayButtons[id])then frame.buttonsScroll:DeleteChild(displayButtons[id]); displayButtons[id] = nil; end if(parentData and parentData.controlledChildren and not massDelete) then for index, childId in pairs(parentData.controlledChildren) do local childButton = displayButtons[childId]; if(childButton) then childButton:SetGroupOrder(index, #parentData.controlledChildren); end end WeakAuras.Add(parentData); WeakAuras.ReloadOptions2(parentData); WeakAuras.UpdateDisplayButton(parentData); end end StaticPopupDialogs["WEAKAURAS_CONFIRM_DELETE"] = { text = "", button1 = L["Delete"], button2 = L["Cancel"], OnAccept = function(self) if self.data then for _, auraData in pairs(self.data.toDelete) do WeakAuras.DeleteOption(auraData, true) end if self.data.parents then for id in pairs(self.data.parents) do local parentData = WeakAuras.GetData(id) local parentButton = WeakAuras.GetDisplayButton(id) WeakAuras.UpdateGroupOrders(parentData) if(#parentData.controlledChildren == 0) then parentButton:DisableExpand() else parentButton:EnableExpand() end parentButton:SetNormalTooltip() WeakAuras.Add(parentData) WeakAuras.ReloadOptions2(parentData) WeakAuras.UpdateDisplayButton(parentData) end end WeakAuras.SortDisplayButtons() end end, OnCancel = function(self) if self.data.parents then for id in pairs(self.data.parents) do local parentRegion = WeakAuras.GetRegion(id) if parentRegion.Resume then parentRegion:Resume() end end end self.data = nil end, showAlert = 1, whileDead = 1, timeout = 0, preferredindex = STATICPOPUP_NUMDIALOGS, } function WeakAuras.ConfirmDelete(toDelete, parents) if toDelete then local warningForm = L["You are about to delete %d aura(s). |cFFFF0000This cannot be undone!|r Would you like to continue?"] StaticPopupDialogs["WEAKAURAS_CONFIRM_DELETE"].text = warningForm:format(#toDelete) StaticPopup_Show("WEAKAURAS_CONFIRM_DELETE", "", "", {toDelete = toDelete, parents = parents}) end end function WeakAuras.OptionsFrame() if(frame) then return frame; else return nil; end end function WeakAuras.ToggleOptions(msg) if(frame and frame:IsVisible()) then WeakAuras.HideOptions(); elseif (InCombatLockdown()) then prettyPrint(L["Options will open after combat ends."]) reopenAfterCombat = true; else WeakAuras.ShowOptions(msg); end end function WeakAuras.UpdateCloneConfig(data) if(WeakAuras.CanHaveClones(data)) then local cloneRegion = WeakAuras.EnsureClone(data.id, 1); cloneRegion:Expand(); cloneRegion = WeakAuras.EnsureClone(data.id, 2); cloneRegion:Expand(); WeakAuras.SetIconNames(data); end end function WeakAuras.ShowOptions(msg) local firstLoad = not(frame); WeakAuras.Pause(); WeakAuras.SetFakeStates() if (firstLoad) then frame = WeakAuras.CreateFrame(); frame.buttonsScroll.frame:Show(); WeakAuras.AddOption(tempGroup.id, tempGroup); WeakAuras.LayoutDisplayButtons(msg); end frame.buttonsScroll.frame:Show(); if (frame.needsSort) then WeakAuras.SortDisplayButtons(); frame.needsSort = nil; end frame:Show(); if (WeakAuras.mouseFrame) then WeakAuras.mouseFrame:OptionsOpened(); end if not(firstLoad) then -- Show what was last shown WeakAuras.PauseAllDynamicGroups(); for id, button in pairs(displayButtons) do if (button:GetVisibility() > 0) then button:PriorityShow(button:GetVisibility()); end end WeakAuras.ResumeAllDynamicGroups(); end if (frame.pickedDisplay) then if (WeakAuras.IsPickedMultiple()) then local children = {} for k,v in pairs(tempGroup.controlledChildren) do children[k] = v end frame:PickDisplayBatch(children); else WeakAuras.PickDisplay(frame.pickedDisplay); end else frame:NewAura(); end if (frame.window == "codereview") then frame.codereview:Close(); end end function WeakAuras.HideOptions() if(frame) then frame:Hide() end end function WeakAuras.IsOptionsOpen() if(frame and frame:IsVisible()) then return true; else return false; end end function WeakAuras.SetIconNames(data) WeakAuras.SetIconName(data, WeakAuras.regions[data.id].region); if(WeakAuras.clones[data.id]) then for index, cloneRegion in pairs(WeakAuras.clones[data.id]) do WeakAuras.SetIconName(data, cloneRegion); end end end function WeakAuras.SetIconName(data, region) local name, icon = WeakAuras.GetNameAndIcon(data); WeakAuras.transmitCache[data.id] = icon; if(region.SetIcon) then region:SetIcon(icon); end if(region.SetName) then region:SetName(name); end end function WeakAuras.GetSortedOptionsLists() local loadedSorted, unloadedSorted = {}, {}; local to_sort = {}; for id, data in pairs(db.displays) do if(data.parent) then -- Do nothing; children will be added later elseif(loaded[id]) then tinsert(to_sort, id); end end table.sort(to_sort, function(a, b) return a < b end); for _, id in ipairs(to_sort) do tinsert(loadedSorted, id); local data = WeakAuras.GetData(id); local controlledChildren = data.controlledChildren; if(controlledChildren) then for _, childId in pairs(controlledChildren) do tinsert(loadedSorted, childId); end end end wipe(to_sort); for id, data in pairs(db.displays) do if(data.parent) then -- Do nothing; children will be added later elseif not(loaded[id]) then tinsert(to_sort, id); end end table.sort(to_sort, function(a, b) return a < b end); for _, id in ipairs(to_sort) do tinsert(unloadedSorted, id); local data = WeakAuras.GetData(id); local controlledChildren = data.controlledChildren; if(controlledChildren) then for _, childId in pairs(controlledChildren) do tinsert(unloadedSorted, childId); end end end return loadedSorted, unloadedSorted; end function WeakAuras.BuildOptions(list, callback) frame:SetLoadProgressVisible(true) local total = 0; for _,_ in pairs(list) do total = total + 1; end local func = function() local num = 0; for id, data in pairs(list) do if(data) then if not(data.regionType == "group" or data.regionType == "dynamicgroup") then WeakAuras.AddOption(id, data); num = num + 1; end end frame.loadProgress:SetText(L["Creating options: "]..num.."/"..total); coroutine.yield(); end callback(); frame:SetLoadProgressVisible(false) end local co = coroutine.create(func); dynFrame:AddAction("BuildOptions", co); end function WeakAuras.LayoutDisplayButtons(msg) local total = 0; for _,_ in pairs(db.displays) do total = total + 1; end local loadedSorted, unloadedSorted = WeakAuras.GetSortedOptionsLists(); frame:SetLoadProgressVisible(true) --frame.buttonsScroll:AddChild(frame.newButton); --if(frame.addonsButton) then -- frame.buttonsScroll:AddChild(frame.addonsButton); --end frame.buttonsScroll:AddChild(frame.loadedButton); frame.buttonsScroll:AddChild(frame.unloadedButton); local func2 = function() local num = frame.loadProgressNum or 0; for index, id in pairs(unloadedSorted) do local data = WeakAuras.GetData(id); if(data) then WeakAuras.EnsureDisplayButton(data); WeakAuras.UpdateDisplayButton(data); frame.buttonsScroll:AddChild(displayButtons[data.id]); WeakAuras.SetIconNames(data); if(WeakAuras.regions[data.id].region.SetStacks) then WeakAuras.regions[data.id].region:SetStacks(1); end if (num % 50 == 0) then frame.buttonsScroll:ResumeLayout() frame.buttonsScroll:PerformLayout() frame.buttonsScroll:PauseLayout() end num = num + 1; end frame.loadProgress:SetText(L["Creating buttons: "]..num.."/"..total); frame.loadProgressNum = num; coroutine.yield(); end frame.buttonsScroll:ResumeLayout() frame.buttonsScroll:PerformLayout() WeakAuras.SortDisplayButtons(msg); WeakAuras.PauseAllDynamicGroups(); if (WeakAuras.IsOptionsOpen()) then for id, button in pairs(displayButtons) do if(loaded[id] ~= nil) then button:PriorityShow(1); end if WeakAurasCompanion and not button.data.parent then -- initialize update icons on top level buttons button:RefreshUpdate() end end end WeakAuras.ResumeAllDynamicGroups(); frame:SetLoadProgressVisible(false) end local func1 = function() local num = frame.loadProgressNum or 0; frame.buttonsScroll:PauseLayout() for index, id in pairs(loadedSorted) do local data = WeakAuras.GetData(id); if(data) then WeakAuras.EnsureDisplayButton(data); WeakAuras.UpdateDisplayButton(data); local button = displayButtons[data.id] frame.buttonsScroll:AddChild(button); WeakAuras.SetIconNames(data); if(WeakAuras.regions[data.id].region.SetStacks) then WeakAuras.regions[data.id].region:SetStacks(1); end num = num + 1; end if (num % 50 == 0) then frame.buttonsScroll:ResumeLayout() frame.buttonsScroll:PerformLayout() frame.buttonsScroll:PauseLayout() end frame.loadProgress:SetText(L["Creating buttons: "]..num.."/"..total); frame.loadProgressNum = num; coroutine.yield(); end local co2 = coroutine.create(func2); dynFrame:AddAction("LayoutDisplayButtons2", co2); end local co1 = coroutine.create(func1); dynFrame:AddAction("LayoutDisplayButtons1", co1); end local function removeFuncs(intable) for i,v in pairs(intable) do if(i == "get" or i == "set" or i == "hidden" or i == "disabled") then intable[i] = nil; elseif(type(v) == "table" and i ~= "values") then removeFuncs(v); end end end local function hiddenChild(childOptionTable, info) for i=#childOptionTable,0,-1 do if(childOptionTable[i].hidden ~= nil) then if(type(childOptionTable[i].hidden) == "boolean") then return childOptionTable[i].hidden; elseif(type(childOptionTable[i].hidden) == "function") then return childOptionTable[i].hidden(info); end end end return false; end local function disabledChild(childOptionTable, info) for i=#childOptionTable,0,-1 do if(childOptionTable[i].disabled ~= nil) then if(type(childOptionTable[i].disabled) == "boolean") then return childOptionTable[i].disabled; elseif(type(childOptionTable[i].disabled) == "function") then return childOptionTable[i].disabled(info); end end end return false; end local function disabeldOrHiddenChild(childOptionTable, info) return hiddenChild(childOptionTable, info) or disabledChild(childOptionTable, info); end local function getChildOption(displayOptions, info) for i=1,#info do displayOptions = displayOptions.args[info[i]]; if not(displayOptions) then return nil; end if (displayOptions.hidden) then local type = type(displayOptions.hidden); if (type == "bool") then if (displayOptions.hidden) then return nil; end elseif (type == "function") then if (displayOptions.hidden(info)) then return nil; end end end end return displayOptions end local function getAll(data, info, ...) local combinedValues = {}; local first = true; local debug = false; local isToggle = nil for index, childId in ipairs(data.controlledChildren) do if isToggle == nil then local childOptions = getChildOption(displayOptions[childId], info) isToggle = childOptions and childOptions.type == "toggle" end local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOptions = displayOptions[childId]; local childOption = childOptions; local childOptionTable = {[0] = childOption}; for i=1,#info do childOption = childOption.args[info[i]]; childOptionTable[i] = childOption; end if (childOption and not hiddenChild(childOptionTable, info)) then for i=#childOptionTable,0,-1 do if(childOptionTable[i].get) then local values = {childOptionTable[i].get(info, ...)}; if isToggle and values[1] == nil then values[1] = false end if(first) then combinedValues = values; first = false; else local same = true; if(#combinedValues == #values) then for j=1,#combinedValues do if(type(combinedValues[j]) == "number" and type(values[j]) == "number") then if((math.floor(combinedValues[j] * 100) / 100) ~= (math.floor(values[j] * 100) / 100)) then same = false; break; end else if(combinedValues[j] ~= values[j]) then same = false; break; end end end else same = false; end if not(same) then return nil; end end break; end end end end if (debug) then print(" \n") end end return unpack(combinedValues); end WeakAuras.getAll = getAll local function setAll(data, info, ...) WeakAuras.pauseOptionsProcessing(true); WeakAuras.PauseAllDynamicGroups() local before = getAll(data, info, ...) for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOptions = displayOptions[childId]; local childOption = childOptions; local childOptionTable = {[0] = childOption}; for i=1,#info do childOption = childOption.args[info[i]]; childOptionTable[i] = childOption; end if (childOption and not disabeldOrHiddenChild(childOptionTable, info)) then for i=#childOptionTable,0,-1 do if(childOptionTable[i].set) then if (childOptionTable[i].type == "multiselect") then childOptionTable[i].set(info, ..., not before); else childOptionTable[i].set(info, ...); end break; end end end end end WeakAuras.ResumeAllDynamicGroups() WeakAuras.pauseOptionsProcessing(false); WeakAuras.ScanForLoads(); WeakAuras.SortDisplayButtons(); end WeakAuras.setAll = setAll local function hiddenAll(data, info) if(#data.controlledChildren == 0 and info[1] ~= "group") then return true; end for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOptions = displayOptions[childId]; local childOption = childOptions; local childOptionTable = {[0] = childOption}; for i=1,#info do childOption = childOption.args[info[i]]; childOptionTable[i] = childOption; end if (childOption) then if (not hiddenChild(childOptionTable, info)) then return false; end end end end return true; end local function disabledAll(data, info) for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOptions = displayOptions[childId]; local childOption = childOptions; local childOptionTable = {[0] = childOption}; for i=1,#info do childOption = childOption.args[info[i]]; childOptionTable[i] = childOption; end if (childOption) then if (not disabledChild(childOptionTable, info)) then return false; end end end end return true; end local function replaceNameDescFuncs(intable, data) local function compareTables(tableA, tableB) if(#tableA == #tableB) then for j=1,#tableA do if(type(tableA[j]) == "number" and type(tableB[j]) == "number") then if((math.floor(tableA[j] * 100) / 100) ~= (math.floor(tableB[j] * 100) / 100)) then return false; end else if(tableA[j] ~= tableB[j]) then return false; end end end else return false; end return true; end local function getValueFor(displayOptions, info, key) local childOptionTable = {[0] = displayOptions}; for i=1,#info do displayOptions = displayOptions.args[info[i]]; if (not displayOptions) then return nil; end childOptionTable[i] = displayOptions; end if (hiddenChild(childOptionTable, info)) then return nil; end for i=#childOptionTable,0,-1 do if(childOptionTable[i][key]) then return childOptionTable[i][key]; end end return nil; end local function combineKeys(info) local combinedKeys = nil; for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local values = getValueFor(displayOptions[childId], info, "values"); if (values) then if (type(values) == "function") then values = values(info); end if (type(values) == "table") then combinedKeys = combinedKeys or {}; for k, v in pairs(values) do combinedKeys[k] = v; end end end end end return combinedKeys; end local function regionPrefix(input) local index = string.find(input, ".", 1, true); if (index) then local regionType = string.sub(input, 1, index - 1); return regionOptions[regionType] and regionType; end return nil; end local function sameAll(info) local combinedValues = {}; local first = true; local combinedKeys = combineKeys(info); local isToggle = nil for index, childId in ipairs(data.controlledChildren) do if isToggle == nil then local childOption = getChildOption(displayOptions[childId], info) isToggle = childOption and childOption.type == "toggle" end local childData = WeakAuras.GetData(childId); local regionType = regionPrefix(info[#info]); if(childData and (not regionType or childData.regionType == regionType or regionType == "sub")) then WeakAuras.EnsureOptions(childId); local childOptions = displayOptions[childId]; local get = getValueFor(displayOptions[childId], info, "get"); if (combinedKeys) then for key, _ in pairs(combinedKeys) do local values = {}; if (get) then values = { get(info, key) }; end if (combinedValues[key] == nil) then combinedValues[key] = values; else if (not compareTables(combinedValues[key], values)) then return nil; end end end else local values = {}; if (get) then values = { get(info) }; local childOption = getChildOption(displayOptions[childId], info) if isToggle and values[1] == nil then values[1] = false end end if(first) then combinedValues = values; first = false; else if (not compareTables(combinedValues, values)) then return nil; end end end end end return true; end local function nameAll(info) local combinedName; local first = true; local foundNames = {}; for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOption = getChildOption(displayOptions[childId], info); if (childOption) then local name; if(type(childOption.name) == "function") then name = childOption.name(info); else name = childOption.name; end if (not name) then -- Do nothing elseif(first) then if (combinedName ~= "") then combinedName = name; first = false; end foundNames[name] = true; elseif not(foundNames[name]) then if (name ~= "") then if (childOption.type == "description") then combinedName = combinedName .. "\n\n" .. name; else combinedName = combinedName .. " / " .. name; end end foundNames[name] = true; end end end end return combinedName; end local function descAll(info) local combinedDesc; local first = true; for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOption = getChildOption(displayOptions[childId], info); if (childOption) then local desc; if(type(childOption.desc) == "function") then desc = childOption.desc(info); else desc = childOption.desc; end if(first) then combinedDesc = desc; first = false; elseif not(combinedDesc == desc) then return L["Not all children have the same value for this option"]; end end end end return combinedDesc; end local function recurse(intable) for i,v in pairs(intable) do if(i == "name" and type(v) ~= "table") then intable.name = function(info) local name = nameAll(info); if(sameAll(info)) then return name; else if(name == "") then return name; else return "|cFF4080FF"..(name or "error"); end end end intable.desc = function(info) if(sameAll(info)) then return descAll(info); else local combinedKeys = nil; if (intable.type == "multiselect") then combinedKeys = combineKeys(info) end local values = {}; for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOptions = displayOptions[childId]; local childOption = childOptions; local childOptionTable = {[0] = childOption}; for i=1,#info do childOption = childOption.args[info[i]]; childOptionTable[i] = childOption; end if (childOption and not hiddenChild(childOptionTable, info)) then for i=#childOptionTable,0,-1 do if(childOptionTable[i].get) then if(intable.type == "toggle") then local name, tri; if(type(childOption.name) == "function") then name = childOption.name(info); tri = true; else name = childOption.name; end if(tri and childOptionTable[i].get(info)) then tinsert(values, "|cFFE0E000"..childId..": |r"..name); elseif(tri) then tinsert(values, "|cFFE0E000"..childId..": |r"..L["Ignored"]); elseif(childOptionTable[i].get(info)) then tinsert(values, "|cFFE0E000"..childId..": |r|cFF00FF00"..L["Enabled"]); else tinsert(values, "|cFFE0E000"..childId..": |r|cFFFF0000"..L["Disabled"]); end elseif(intable.type == "color") then local r, g, b = childOptionTable[i].get(info); r, g, b = r or 1, g or 1, b or 1; tinsert(values, ("|cFF%2x%2x%2x%s"):format(r * 220 + 35, g * 220 + 35, b * 220 + 35, childId)); elseif(intable.type == "select") then local selectValues = type(intable.values) == "table" and intable.values or intable.values(info); local key = childOptionTable[i].get(info); local display = key and selectValues[key] or L["None"]; if intable.dialogControl == "LSM30_Font" then tinsert(values, "|cFFE0E000"..childId..": |r" .. key); else tinsert(values, "|cFFE0E000"..childId..": |r"..display); end elseif(intable.type == "multiselect") then local selectedValues = {}; for k, v in pairs(combinedKeys) do if (childOptionTable[i].get(info, k)) then tinsert(selectedValues, tostring(v)) end end tinsert(values, "|cFFE0E000"..childId..": |r"..table.concat(selectedValues, ",")); else local display = childOptionTable[i].get(info) or L["None"]; if(type(display) == "number") then display = math.floor(display * 100) / 100; else if #display > 50 then display = display:sub(1, 50) .. "..." end end tinsert(values, "|cFFE0E000"..childId..": |r"..display); end break; end end end end end return table.concat(values, "\n"); end end elseif(type(v) == "table" and i ~= "values") then recurse(v); end end end recurse(intable); end local function replaceImageFuncs(intable, data) local function imageAll(info) local combinedImage = {}; local first = true; for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOption = displayOptions[childId]; if not(childOption) then return "error" end childOption = getChildOption(childOption, info); if childOption and childOption.image then local image = {childOption.image(info)}; if(first) then combinedImage = image; first = false; else if not(combinedImage[1] == image[1]) then return "", 0, 0; end end end end end return unpack(combinedImage); end local function recurse(intable) for i,v in pairs(intable) do if(i == "image" and type(v) == "function") then intable[i] = imageAll; elseif(type(v) == "table" and i ~= "values") then recurse(v); end end end recurse(intable); end local function replaceValuesFuncs(intable, data) local function valuesAll(info) local combinedValues = {}; local handledValues = {}; local first = true; for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then WeakAuras.EnsureOptions(childId); local childOption = displayOptions[childId]; if not(childOption) then return "error" end childOption = getChildOption(childOption, info); if (childOption) then local values = childOption.values; if (type(values) == "function") then values = values(info); end if(first) then for k, v in pairs(values) do handledValues[k] = handledValues[k] or {}; handledValues[k][v] = true; combinedValues[k] = v; end first = false; else for k, v in pairs(values) do if (handledValues[k] and handledValues[k][v]) then -- Already known key/value pair else if (combinedValues[k]) then combinedValues[k] = combinedValues[k] .. "/" .. v; else combinedValues[k] = v; end handledValues[k] = handledValues[k] or {}; handledValues[k][v] = true; end end end end end end return combinedValues; end local function recurse(intable) for i,v in pairs(intable) do if(i == "values" and type(v) == "function") then intable[i] = valuesAll; elseif(type(v) == "table" and i ~= "values") then recurse(v); end end end recurse(intable); end local function GetCustomCode(data, path) for _, key in ipairs(path) do if (not data or not data[key]) then return nil; end data = data[key]; end return data; end -- TODO: find a paradigm which doesn't have five million flags for AddCodeOption so that calls to it don't always go off the screen -- a table of settings is the obvious option to pack the flags. -- alternatively, we could create a "code" type option which then gets processed before sending to AceConfig function WeakAuras.AddCodeOption(args, data, name, prefix, url, order, hiddenFunc, path, encloseInFunction, multipath, extraSetFunction, extraFunctions, reloadOptions, setOnParent) extraFunctions = extraFunctions or {}; tinsert(extraFunctions, 1, { buttonLabel = L["Expand"], func = function() WeakAuras.OpenTextEditor(data, path, encloseInFunction, multipath, reloadOptions, setOnParent, url) end }); args[prefix .. "_custom"] = { type = "input", width = WeakAuras.doubleWidth, name = name, order = order, multiline = true, hidden = hiddenFunc, control = "WeakAurasMultiLineEditBox", arg = { extraFunctions = extraFunctions, }, set = function(info, v) local subdata = data; for i = 1, #path -1 do local key = path[i]; subdata[key] = subdata[key] or {}; subdata = subdata[key]; end subdata[path[#path]] = v; WeakAuras.Add(data); if (extraSetFunction) then extraSetFunction(); end if (reloadOptions) then WeakAuras.ScheduleReloadOptions(data); end end, get = function(info) return GetCustomCode(data, path); end }; args[prefix .. "_customError"] = { type = "description", name = function() if hiddenFunc() then return ""; end local code = GetCustomCode(data, path); if (not code) then return "" end if (encloseInFunction) then code = "function() "..code.."\n end"; end code = "return " .. code; local _, errorString = loadstring(code); return errorString and "|cFFFF0000"..errorString or ""; end, width = WeakAuras.doubleWidth, order = order + 0.002, hidden = function() if (hiddenFunc()) then return true; end local code = GetCustomCode(data, path); if (not code) then return true; end if (encloseInFunction) then code = "function() "..code.."\n end"; end code = "return " .. code; local loadedFunction, errorString = loadstring(code); if(errorString and not loadedFunction) then return false; else return true; end end }; end local function addCollapsibleHeader(options, key, input, order, isGroupTab) if input.__noHeader then return end local title = input.__title local hasDelete = input.__delete local hasUp = input.__up local hasDown = input.__down local hasDuplicate = input.__duplicate local hiddenFunc = input.__hidden local nooptions = input.__nooptions local marginTop = input.__topLine local titleWidth = WeakAuras.doubleWidth - (hasDelete and 0.15 or 0) - (hasUp and 0.15 or 0) - (hasDown and 0.15 or 0) - (hasDuplicate and 0.15 or 0) options[key .. "collapseSpacer"] = { type = marginTop and "header" or "description", name = "", order = order, width = "full", hidden = hiddenFunc, } options[key .. "collapseButton"] = { type = "execute", name = title, order = order + 0.1, width = titleWidth, func = function(info) if not nooptions then local isCollapsed = WeakAuras.IsCollapsed("collapse", "region", key, false) WeakAuras.SetCollapsed("collapse", "region", key, not isCollapsed) WeakAuras.RefillOptions() end end, image = function() if nooptions then return "Interface\\AddOns\\WeakAuras\\Media\\Textures\\bullet1", 18, 18 else local isCollapsed = WeakAuras.IsCollapsed("collapse", "region", key, false) return isCollapsed and "Interface\\AddOns\\WeakAuras\\Media\\Textures\\expand" or "Interface\\AddOns\\WeakAuras\\Media\\Textures\\collapse", 18, 18 end end, control = "WeakAurasExpand", hidden = hiddenFunc } if hasUp then options[key .. "upButton"] = { type = "execute", name = "", order = order + 0.3, width = 0.15, func = input.__up, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\moveup", imageWidth = 24, imageHeight = 24, hidden = hiddenFunc, } end if hasDown then options[key .. "downButton"] = { type = "execute", name = "", order = order + 0.4, width = 0.15, func = input.__down, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\movedown", imageWidth = 24, imageHeight = 24, hidden = hiddenFunc } end if hasDuplicate then options[key .. "duplicateButton"] = { type = "execute", name = "", order = order + 0.5, width = 0.15, func = input.__duplicate, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\duplicate", imageWidth = 24, imageHeight = 24, hidden = hiddenFunc } end if hasDelete then options[key .. "deleteButton"] = { type = "execute", name = "", order = order + 0.6, width = 0.15, func = input.__delete, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\delete", imageWidth = 24, imageHeight = 24, hidden = hiddenFunc } end if hiddenFunc then return function() return hiddenFunc() or WeakAuras.IsCollapsed("collapse", "region", key, false) end else return function() return WeakAuras.IsCollapsed("collapse", "region", key, false) end end end local function copyOptionTable(input, orderAdjustment, collapsedFunc) local resultOption = {}; WeakAuras.DeepCopy(input, resultOption); resultOption.order = orderAdjustment + resultOption.order; if collapsedFunc then local oldHidden = resultOption.hidden; if oldHidden ~= nil then local oldFunc if type(oldHidden) ~= "function" then oldFunc = function(...) return oldHidden end else oldFunc = oldHidden end resultOption.hidden = function(...) if collapsedFunc() then return true else return oldFunc(...) end end else resultOption.hidden = collapsedFunc; end end return resultOption; end local function flattenRegionOptions(allOptions, isGroupTab) local result = {}; local base = 1000; for optionGroup, options in pairs(allOptions) do local groupBase = base * options.__order local collapsedFunc = addCollapsibleHeader(result, optionGroup, options, groupBase, isGroupTab) for optionName, option in pairs(options) do if not optionName:find("^__") then result[optionGroup .. "." .. optionName] = copyOptionTable(option, groupBase, collapsedFunc); end end end return result; end local function parsePrefix(input, data, create) local subRegionIndex, property = string.match(input, "^sub%.(%d+)%..+%.(.+)") subRegionIndex = tonumber(subRegionIndex) if subRegionIndex then if create then data.subRegions = data.subRegions or {} data.subRegions[subRegionIndex] = data.subRegions[subRegionIndex] or {} else if not data.subRegions or not data.subRegions[subRegionIndex] then return nil end end return data.subRegions[subRegionIndex], property end local index = string.find(input, ".", 1, true); if (index) then return data, string.sub(input, index + 1); end return data, input end local function AddSubRegionImpl(data, subRegionName) data.subRegions = data.subRegions or {} if WeakAuras.subRegionTypes[subRegionName] and WeakAuras.subRegionTypes[subRegionName] then if WeakAuras.subRegionTypes[subRegionName].supports(data.regionType) then local default = WeakAuras.subRegionTypes[subRegionName].default local subRegionData = type(default) == "function" and default(data.regionType) or CopyTable(default) subRegionData.type = subRegionName tinsert(data.subRegions, subRegionData) WeakAuras.Add(data) WeakAuras.ReloadOptions2(data.id, data) end end end local function AddSubRegion(data, subRegionName) WeakAuras.ApplyToDataOrChildData(data, AddSubRegionImpl, subRegionName) WeakAuras.ReloadOptions2(data.id, data) end local function AddOptionsForSupportedSubRegion(regionOption, data, supported) if not next(supported) then return end local hasSubRegions = false local result = {} local order = 1 result.__order = 300 result.__title = L["Add Extra Elements"] result.__topLine = true for subRegionType in pairs(supported) do if WeakAuras.subRegionTypes[subRegionType].supportsAdd then hasSubRegions = true result[subRegionType .. "space"] = { type = "description", width = WeakAuras.doubleWidth, name = "", order = order, } order = order + 1 result[subRegionType] = { type = "execute", width = WeakAuras.normalWidth, name = string.format(L["Add %s"], WeakAuras.subRegionTypes[subRegionType].displayName), order = order, func = function() AddSubRegion(data, subRegionType) end, } order = order + 1 end end regionOption["sub"] = result; return hasSubRegions end function WeakAuras.AddOption(id, data) local regionOption; local commonOption = {}; local hasSubElements = false if(regionOptions[data.regionType]) then regionOption = regionOptions[data.regionType].create(id, data); if data.subRegions then local subIndex = {} for index, subRegionData in ipairs(data.subRegions) do local subRegionType = subRegionData.type if WeakAuras.subRegionOptions[subRegionType] then hasSubElements = true subIndex[subRegionType] = subIndex[subRegionType] and subIndex[subRegionType] + 1 or 1 local options, common = WeakAuras.subRegionOptions[subRegionType].create(data, subRegionData, index, subIndex[subRegionType]) options.__order = 200 + index regionOption["sub." .. index .. "." .. subRegionType] = options commonOption[subRegionType] = common end end end local commonOptionIndex = 0 for option, optionData in pairs(commonOption) do commonOptionIndex = commonOptionIndex + 1 optionData.__order = 100 + commonOptionIndex regionOption[option] = optionData end local supported = {} for subRegionName, subRegionType in pairs(WeakAuras.subRegionTypes) do if subRegionType.supports(data.regionType) then supported[subRegionName] = true end end hasSubElements = AddOptionsForSupportedSubRegion(regionOption, data, supported) or hasSubElements else regionOption = { [data.regionType] = { __title = "|cFFFFFF00" .. data.regionType, __order = 1, unsupported = { type = "description", name = L["This region of type \"%s\" is not supported."]:format(data.regionType), order = 2, } } }; end if hasSubElements then regionOption["SubElementsHeader"] = { __order = 100, __noHeader = true, header = { type = "header", name = L["Sub Elements"], order = 1 } } end local options = flattenRegionOptions(regionOption, true) displayOptions[id] = { type = "group", childGroups = "tab", args = { region = { type = "group", name = L["Display"], order = 10, get = function(info) local base, property = parsePrefix(info[#info], data); if not base then return nil end if(info.type == "color") then base[property] = base[property] or {}; local c = base[property]; return c[1], c[2], c[3], c[4]; else return base[property]; end end, set = function(info, v, g, b, a) local base, property = parsePrefix(info[#info], data, true); if(info.type == "color") then base[property] = base[property] or {}; local c = base[property]; c[1], c[2], c[3], c[4] = v, g, b, a; elseif(info.type == "toggle") then base[property] = v; else base[property] = (v ~= "" and v) or nil; end WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); if(data.parent) then local parentData = WeakAuras.GetData(data.parent); if(parentData) then WeakAuras.Add(parentData); end end WeakAuras.ResetMoverSizer(); end, args = options }, trigger = { type = "group", name = L["Trigger"], order = 20, args = {} }, conditions = { type = "group", name = L["Conditions"], order = 25, args = {} }, load = { type = "group", name = L["Load"], order = 30, get = function(info) return data.load[info[#info]] end, set = function(info, v) data.load[info[#info]] = (v ~= "" and v) or nil; WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.ScanForLoads({[data.id] = true}); WeakAuras.SortDisplayButtons(); end, args = {} }, authorOptions = { type = "group", name = L["Custom Options"], order = 100 } } }; displayOptions[id].args.action = WeakAuras.AddActionOption(id, data); displayOptions[id].args.animation = WeakAuras.AddAnimationOption(id, data); WeakAuras.ReloadTriggerOptions(data); end -- This is a hack... -- Some options change which options are available, for example toggling the "inverse" -- option of some triggers changes whether "remaining time" is available in the Conditions -- We can't call ReloadOptions from the set call, since that removes the widgets immediately -- which AceConfig doesn't like. -- Thus Reload the options after a very small delay. function WeakAuras.ScheduleReloadOptions(data) if (type(data.id) ~= "table") then C_Timer.After(0.1, function() WeakAuras.ReloadOptions(data.id) end ); end end function WeakAuras.ReloadOptions(id) displayOptions[id] = nil; WeakAuras.EnsureOptions(id); WeakAuras.RefillOptions() end function WeakAuras.EnsureOptions(id) if not(displayOptions[id]) then WeakAuras.AddOption(id, WeakAuras.GetData(id)); end end local scheduleRefillFrame = CreateFrame("FRAME", "", UIParent) scheduleRefillFrame.OnUpdate = function() for id, data in pairs(scheduleRefillFrame.ids) do if not(displayOptions[id]) then WeakAuras.AddOption(id, data); end end wipe(scheduleRefillFrame.ids) frame:RefillOptions() frame:SetScript("OnUpdate", nil) end scheduleRefillFrame.ScheduleRefillOnly = function() frame:SetScript("OnUpdate", scheduleRefillFrame.OnUpdate) end scheduleRefillFrame.ScheduleReload = function(self, id, data) scheduleRefillFrame.ids[id] = data scheduleRefillFrame:ScheduleRefillOnly() end scheduleRefillFrame.ids = {} -- TODO replace older ReloadOptions, ScheduleReloadOptions, ReloadTriggerOptions, ReloadGroupRegionOptions -- automatically clear parent/tempGroup ? function WeakAuras.ReloadOptions2(id, data) displayOptions[id] = nil scheduleRefillFrame:ScheduleReload(id, data) end function WeakAuras.RefillOptions() frame:RefillOptions() end function WeakAuras.ScheduleRefillOptions() scheduleRefillFrame:ScheduleRefillOnly() end function WeakAuras.GetSpellTooltipText(id) local tooltip = WeakAuras.GetHiddenTooltip(); tooltip:SetSpellByID(id); local lines = { tooltip:GetRegions() }; local i = 1; local tooltipText = ""; while(lines[i]) do if(lines[i]:GetObjectType() == "FontString") then if(lines[i]:GetText()) then if(tooltipText == "") then tooltipText = lines[i]:GetText(); else tooltipText = tooltipText.." - "..lines[i]:GetText(); end end end i = i + 1; end tooltipText = tooltipText or L["No tooltip text"]; return tooltipText; end local function DeleteConditionsForTriggerHandleSubChecks(checks, triggernum) for _, check in ipairs(checks) do if (check.trigger == triggernum) then check.trigger = nil; end if (check.trigger and check.trigger > triggernum) then check.trigger = check.trigger - 1; end if (checks.checks) then DeleteConditionsForTriggerHandleSubChecks(checks.checks, triggernum); end end end function WeakAuras.DeleteConditionsForTrigger(data, triggernum) for _, condition in ipairs(data.conditions) do if (condition.check and condition.check.trigger == triggernum) then condition.check.trigger = nil; end if (condition.check and condition.check.trigger and condition.check.trigger > triggernum) then condition.check.trigger = condition.check.trigger - 1; end if (condition.check and condition.check.checks) then DeleteConditionsForTriggerHandleSubChecks(condition.check.checks, triggernum) end end end function WeakAuras.ReloadTriggerOptions(data) local id = data.id; local iconCache = spellCache.Get(); WeakAuras.EnsureOptions(id); local appendToTriggerPath, appendToUntriggerPath; if(data.controlledChildren) then optionTriggerChoices[id] = nil; for index, childId in pairs(data.controlledChildren) do if not(optionTriggerChoices[id]) then optionTriggerChoices[id] = optionTriggerChoices[childId]; else if(optionTriggerChoices[id] ~= optionTriggerChoices[childId]) then optionTriggerChoices[id] = -1; break; end end end optionTriggerChoices[id] = optionTriggerChoices[id] or 1; local commonOptionTriggerChoice = optionTriggerChoices[id] >= 1 and optionTriggerChoices[id]; for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then optionTriggerChoices[childId] = commonOptionTriggerChoice or optionTriggerChoices[childId] or 1; WeakAuras.ReloadTriggerOptions(childData); end end else optionTriggerChoices[id] = min(optionTriggerChoices[id] or 1, #data.triggers); local triggerChoice = optionTriggerChoices[id] -- TODO: remove this once legacy aura trigger is removed local button = displayButtons[id] if (button) then button:RefreshBT2UpgradeIcon() end end local function deleteTrigger() if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then if #childData.triggers > 1 then tremove(childData.triggers, optionTriggerChoices[childId]) WeakAuras.DeleteConditionsForTrigger(childData, optionTriggerChoices[childId]); optionTriggerChoices[childId] = max(1, optionTriggerChoices[childId] - 1) WeakAuras.ReloadTriggerOptions(childData); end end end else tremove(data.triggers, optionTriggerChoices[id]) WeakAuras.DeleteConditionsForTrigger(data, optionTriggerChoices[id]); optionTriggerChoices[id] = max(1, optionTriggerChoices[id] - 1) end WeakAuras.Add(data); WeakAuras.ReloadTriggerOptions(data); end local function moveTriggerDownConditionCheck(check, i) if (check.trigger == i) then check.trigger = i + 1; elseif (check.trigger == i + 1) then check.trigger = i; end if (check.checks) then for _, subCheck in ipairs(check.checks) do moveTriggerDownConditionCheck(subCheck, i); end end end local function moveTriggerDownImpl(data, i) if (i < 1 or i >= #data.triggers) then return false; end data.triggers[i], data.triggers[i + 1] = data.triggers[i + 1], data.triggers[i] for _, condition in ipairs(data.conditions) do moveTriggerDownConditionCheck(condition.check, i); end return true; end local function moveTriggerDown(data, i) if (moveTriggerDownImpl(data, i)) then optionTriggerChoices[data.id] = optionTriggerChoices[data.id] + 1; WeakAuras.Add(data); WeakAuras.ReloadTriggerOptions(data); end end local function moveTriggerUp(data, i) if (moveTriggerDownImpl(data, i - 1)) then optionTriggerChoices[data.id] = optionTriggerChoices[data.id] - 1; WeakAuras.Add(data); WeakAuras.ReloadTriggerOptions(data); end end local chooseTriggerWidth = 1.2; if (data.controlledChildren) then local hasMultipleTriggers = false; for index, id in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(id); if (#childData.triggers ~=1) then hasMultipleTriggers = true; break; end end if (not hasMultipleTriggers) then chooseTriggerWidth = chooseTriggerWidth + 0.45; end else if (#data.triggers == 1) then chooseTriggerWidth = chooseTriggerWidth + 0.45; end end local _, _, _, enabled = GetAddOnInfo("WeakAurasTemplates") if (not enabled) then chooseTriggerWidth = chooseTriggerWidth + 0.15; end local trigger; if (not data.controlledChildren) then local triggerNum = optionTriggerChoices[id]; trigger = data.triggers[triggerNum].trigger; end local trigger_types = {}; for type, triggerSystem in pairs(WeakAuras.triggerTypes) do trigger_types[type] = triggerSystem.GetName(type); end local trigger_options = { disjunctive = { type = "select", name = L["Required for Activation"], width = WeakAuras.doubleWidth, order = 0, values = function() if #data.triggers > 1 then return WeakAuras.trigger_require_types; else return WeakAuras.trigger_require_types_one; end end, get = function() if #data.triggers > 1 then return data.triggers.disjunctive or "all"; else return (data.triggers.disjunctive and data.triggers.disjunctive ~= "all") and data.triggers.disjunctive or "any"; end end, set = function(info, v) data.triggers.disjunctive = v; WeakAuras.Add(data); end }, -- custom trigger combiner text editor added below activeTriggerMode = { type = "select", name = L["Dynamic Information"], width = WeakAuras.doubleWidth, order = 0.3, values = function() local vals = {}; vals[WeakAuras.trigger_modes.first_active] = L["Dynamic information from first active trigger"]; for i = 1, #data.triggers do vals[i] = L["Dynamic information from Trigger %i"]:format(i); end return vals; end, get = function() return data.triggers.activeTriggerMode or WeakAuras.trigger_modes.first_active; end, set = function(info, v) data.triggers.activeTriggerMode = v; WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); end, hidden = function() return #data.triggers <= 1 end }, chooseTrigger = { type = "select", name = L["Choose Trigger"], order = 0.5, values = function() local ret = {}; if(data.controlledChildren) then for index = 1, #data.triggers do local all, none, any = true, true, false; for _, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then none = false; if childData.triggers[index] then any = true; else all = false; end end end if not(none) then if(all) then ret[index] = L["Trigger %d"]:format(index); elseif(any) then ret[index] = "|cFF777777"..L["Trigger %d"]:format(index); end end end else for i = 1, #data.triggers do ret[i] = L["Trigger %d"]:format(i); end end return ret; end, get = function() return optionTriggerChoices[id]; end, set = function(info, v) if data.triggers[v] then optionTriggerChoices[id] = v; WeakAuras.ReloadTriggerOptions(data); end end, width = chooseTriggerWidth }, chooseTriggerSpace = { type = "description", name = "", order = 0.75, width = 0.04 }, addTrigger = { type = "execute", name = L["Add Trigger"], order = 1, func = function() if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then tinsert(childData.triggers, {trigger = {}, untrigger = {}}); optionTriggerChoices[childId] = #childData.triggers; WeakAuras.ReloadTriggerOptions(childData); end end else tinsert(data.triggers, {trigger = {}, untrigger = {}}); optionTriggerChoices[id] = #data.triggers; end WeakAuras.ReloadTriggerOptions(data); end, width = 0.15, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\add", imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon" }, deleteTrigger = { type = "execute", name = L["Delete Trigger"], order = 1.1, func = deleteTrigger, hidden = function() return #data.triggers < 2 end, width = 0.15, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\delete", imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon" }, triggerUp = { type = "execute", name = L["Up"], order = 1.2, func = function() if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); moveTriggerUp(childData, optionTriggerChoices[childId]) end WeakAuras.Add(data); WeakAuras.ReloadTriggerOptions(data); else moveTriggerUp(data, optionTriggerChoices[id]) end end, disabled = function() if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then if (optionTriggerChoices[childId] ~= 1) then return false; end end end return true; else if (optionTriggerChoices[id] == 1) then return true; else return false; end end end, hidden = function() return #data.triggers < 2 end, width = 0.15, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\moveup", imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon" }, triggerDown = { type = "execute", name = L["Down"], order = 1.3, func = function() if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); moveTriggerDown(childData, optionTriggerChoices[childId]); end WeakAuras.Add(data); WeakAuras.ReloadTriggerOptions(data); else moveTriggerDown(data, optionTriggerChoices[id]); end end, disabled = function() if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if(childData) then if (optionTriggerChoices[childId] ~= #childData.triggers) then return false; end end end return true; else if (optionTriggerChoices[id] ~= #data.triggers) then return false; else return true; end end end, hidden = function() return #data.triggers < 2 end, width = 0.15, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\movedown", imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon" }, applyTemplate = { type = "execute", name = L["Apply Template"], order = 1.4, func = function() WeakAuras.OpenTriggerTemplate(data); end, hidden = function() return not select(4, GetAddOnInfo("WeakAurasTemplates")) end, width = 0.15, image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\template", imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon" }, triggerHeader = { type = "header", name = function(info) if(info == "default") then return L["Multiple Triggers"]; else return L["Trigger %d"]:format(optionTriggerChoices[id]); end end, order = 2 }, typedesc = { type = "toggle", width = WeakAuras.normalWidth, name = L["Type"], order = 5, disabled = true, get = function() return true end }, type = { type = "select", width = WeakAuras.normalWidth, name = L["Type"], desc = L["The type of trigger"], order = 6, values = trigger_types, set = function(info, v) trigger.type = v; if(trigger.event) then local prototype = WeakAuras.event_prototypes[trigger.event]; if(prototype) then if(v == "status" and prototype.type == "event") then trigger.event = "Health"; elseif(v == "event" and prototype.type == "status") then trigger.event = "Chat Message"; end end end WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.ReloadTriggerOptions(data); end }, }; local function hideTriggerCombiner() return not (data.triggers.disjunctive == "custom") end WeakAuras.AddCodeOption(trigger_options, data, L["Custom"], "custom_trigger_combination", "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#custom-activation", 0.1, hideTriggerCombiner, {"triggers", "customTriggerLogic"}, false); local order = 81; if(data.controlledChildren) then local function options_set(info, ...) setAll(data, info, ...); WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.ReloadTriggerOptions(data); end local commontriggerSystemOptionsFunction = nil; local first = true; local anyOldAuraTriggers = false; for index, childId in ipairs(data.controlledChildren) do local triggerChoice = optionTriggerChoices[childId]; local childData = WeakAuras.GetData(childId); local trigger = triggerChoice and childData.triggers[triggerChoice].trigger; local triggerSystemOptionsFunction = trigger.type and WeakAuras.triggerTypesOptions[trigger.type]; if (trigger.type == "aura") then anyOldAuraTriggers = true; end if (triggerSystemOptionsFunction) then if (first) then commontriggerSystemOptionsFunction = triggerSystemOptionsFunction first = false; elseif(commontriggerSystemOptionsFunction ~= triggerSystemOptionsFunction) then commontriggerSystemOptionsFunction = nil; end end end if (commontriggerSystemOptionsFunction) then trigger_options = union(trigger_options, commontriggerSystemOptionsFunction(data, optionTriggerChoices)); end if (anyOldAuraTriggers) then trigger_options = union(trigger_options, WeakAuras.GetBuffConversionOptions(data, optionTriggerChoices)); end displayOptions[id].args.trigger.args = trigger_options; removeFuncs(displayOptions[id]); replaceNameDescFuncs(displayOptions[id], data); replaceImageFuncs(displayOptions[id], data); replaceValuesFuncs(displayOptions[id], data); displayOptions[id].get = function(info, ...) return getAll(data, info, ...); end; displayOptions[id].set = function(info, ...) setAll(data, info, ...); if(type(id) == "string") then WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.ResetMoverSizer(); end end displayOptions[id].hidden = function(info, ...) return hiddenAll(data, info, ...); end; displayOptions[id].disabled = function(info, ...) return disabledAll(data, info, ...); end; displayOptions[id].args.trigger.args.chooseTrigger.set = options_set; if (displayOptions[id].args.trigger.args.type) then displayOptions[id].args.trigger.args.type.set = options_set; end if (displayOptions[id].args.trigger.args.event) then displayOptions[id].args.trigger.args.event.set = options_set; end local regionOption; if (regionOptions[data.regionType]) then regionOption = regionOptions[data.regionType].create(id, data); else regionOption = { [data.regionType] = { __title = "|cFFFFFF00" .. data.regionType, __order = 1, unsupported = { type = "description", name = L["This region of type \"%s\" is not supported."]:format(data.regionType) } }; }; end displayOptions[id].args.group = { type = "group", name = L["Group"], order = 0, get = function(info) local base, property = parsePrefix(info[#info], data); if not base then return nil end if(info.type == "color") then base[property] = base[property] or {}; local c = base[property]; return c[1], c[2], c[3], c[4]; else return base[property]; end end, set = function(info, v, g, b, a) local base, property = parsePrefix(info[#info], data, true); if(info.type == "color") then base[property] = base[property] or {}; local c = base[property]; c[1], c[2], c[3], c[4] = v, g, b, a; elseif(info.type == "toggle") then base[property] = v; else base[property] = (v ~= "" and v) or nil; end WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.ResetMoverSizer(); end, hidden = function() return false end, disabled = function() return false end, args = flattenRegionOptions(regionOption, true); }; data.load.use_class = getAll(data, {"load", "use_class"}); local single_class = getAll(data, {"load", "class"}); data.load.class = { single = single_class, multi = {}, } displayOptions[id].args.load.args = WeakAuras.ConstructOptions(WeakAuras.load_prototype, data, 10, optionTriggerChoices[id], "load"); removeFuncs(displayOptions[id].args.load); replaceNameDescFuncs(displayOptions[id].args.load, data); replaceImageFuncs(displayOptions[id].args.load, data); replaceValuesFuncs(displayOptions[id].args.load, data); WeakAuras.ReloadGroupRegionOptions(data); else -- One aura selected local triggerChoice = optionTriggerChoices[id]; local trigger, untrigger = data.triggers[triggerChoice].trigger, data.triggers[triggerChoice].untrigger; local function options_set(info, v) trigger[info[#info]] = v; WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.ReloadTriggerOptions(data); end local triggerSystemOptionsFunction = trigger.type and WeakAuras.triggerTypesOptions[trigger.type]; if (triggerSystemOptionsFunction) then trigger_options = union(trigger_options, triggerSystemOptionsFunction(data, optionTriggerChoices)); end if (trigger.type == "aura") then trigger_options = union(trigger_options, WeakAuras.GetBuffConversionOptions(data, optionTriggerChoices)); end displayOptions[id].args.trigger.args = trigger_options; displayOptions[id].args.load.args = WeakAuras.ConstructOptions(WeakAuras.load_prototype, data, 10, optionTriggerChoices[id], "load"); if (displayOptions[id].args.trigger.args.event) then displayOptions[id].args.trigger.args.event.set = function(info, v, ...) local prototype = WeakAuras.event_prototypes[v]; if(prototype) then if(prototype.automatic or prototype.automaticrequired) then trigger.unevent = "auto"; else trigger.unevent = "timed"; end end options_set(info, v, ...); end end trigger.event = trigger.event or "Health"; trigger.subeventPrefix = trigger.subeventPrefix or "SPELL" trigger.subeventSuffix = trigger.subeventSuffix or "_CAST_START"; displayOptions[id].args.trigger.get = function(info, index) local childOption = getChildOption(displayOptions[id], info) if childOption.type == "multiselect" then return trigger[info[#info]] and trigger[info[#info]][index] else return trigger[info[#info]] end end; displayOptions[id].args.trigger.set = function(info, arg1, arg2) local childOption = getChildOption(displayOptions[id], info) if childOption.type == "multiselect" then local index, value = arg1, arg2 if type(trigger[info[#info]]) ~= "table" then trigger[info[#info]] = {} end if value ~= nil then if value then trigger[info[#info]][index] = true else trigger[info[#info]][index] = nil end else if trigger[info[#info]][index] then trigger[info[#info]][index] = nil else trigger[info[#info]][index] = true end end local next = next if next(trigger[info[#info]]) == nil then trigger[info[#info]] = nil end else trigger[info[#info]] = (arg1 ~= "" and arg1) or nil; end WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.SetIconNames(data); WeakAuras.UpdateDisplayButton(data); WeakAuras.ReloadTriggerOptions(data); end; end displayOptions[id].args.authorOptions.args = {} displayOptions[id].args.authorOptions.hidden = function() return false; end displayOptions[id].args.authorOptions.disabled = function() return false; end WeakAuras.GetAuthorOptions(data, displayOptions[id].args.authorOptions.args, 0) displayOptions[id].args.conditions.args = {}; -- We never want the condition options to use the hiddenAll, disabledAll functions displayOptions[id].args.conditions.hidden = function() return false; end displayOptions[id].args.conditions.disabled = function() return false; end WeakAuras.GetConditionOptions(data, displayOptions[id].args.conditions.args, "conditions", 0, nil); if(type(id) ~= "string") then displayOptions[id].args.group = nil; end end local function fixMetaOrders(allOptions) -- assumes that the results from create methods are contiguous in __order fields -- shifts __order fields such that each optionGroup is ordered correctly relative -- to its peers, but has a unique __order number in the combined option table. local groupOrders = {} local maxGroupOrder = 0 for optionGroup, options in pairs(allOptions) do local metaOrder = options.__order groupOrders[metaOrder] = groupOrders[metaOrder] or {} maxGroupOrder = max(maxGroupOrder, metaOrder) tinsert(groupOrders[metaOrder], optionGroup) end local index = 0 local newOrder = 1 while index <= maxGroupOrder do index = index + 1 if groupOrders[index] then table.sort(groupOrders[index]) for _, optionGroup in ipairs(groupOrders[index]) do allOptions[optionGroup].__order = newOrder newOrder = newOrder + 1 end end end end function WeakAuras.ReloadGroupRegionOptions(data) local regionTypes = {}; local id = data.id; WeakAuras.EnsureOptions(id); local options = displayOptions[id]; local allOptions = {}; local commonOption = {}; local unsupportedCount = 0 local supportedSubRegions = {} local hasSubElements = false for index, childId in ipairs(data.controlledChildren) do local childData = WeakAuras.GetData(childId); if childData and not regionTypes[childData.regionType] then regionTypes[childData.regionType] = true; if regionOptions[childData.regionType] then allOptions = union(allOptions, regionOptions[childData.regionType].create(id, data)); else unsupportedCount = unsupportedCount + 1 allOptions["__unsupported" .. unsupportedCount] = { __title = "|cFFFFFF00" .. childData.regionType, __order = 1, warning = { type = "description", name = L["Regions of type \"%s\" are not supported."]:format(childData.regionType), order = 1 }, } end for subRegionName, subRegionType in pairs(WeakAuras.subRegionTypes) do if subRegionType.supports(childData.regionType) then supportedSubRegions[subRegionName] = true end end end if childData.subRegions then local subIndex = {} for index, subRegionData in ipairs(childData.subRegions) do local subRegionType = subRegionData.type if WeakAuras.subRegionOptions[subRegionType] then hasSubElements = true subIndex[subRegionType] = subIndex[subRegionType] and subIndex[subRegionType] + 1 or 1 local options, common = WeakAuras.subRegionOptions[subRegionType].create(data, subRegionData, index, subIndex[subRegionType]) options.__order = 200 + index allOptions["sub." .. index .. "." .. subRegionType] = options commonOption[subRegionType] = common end end end end local commonOptionIndex = 0 for option, optionData in pairs(commonOption) do commonOptionIndex = commonOptionIndex + 1 optionData.__order = 100 + commonOptionIndex allOptions[option] = optionData end hasSubElements = AddOptionsForSupportedSubRegion(allOptions, data, supportedSubRegions) or hasSubElements if hasSubElements then allOptions["SubElementsHeader"] = { __order = 100, __noHeader = true, header = { order = 1, type = "header", name = L["Sub Elements"], } } end fixMetaOrders(allOptions); local regionOption = flattenRegionOptions(allOptions, false); replaceNameDescFuncs(regionOption, data); replaceImageFuncs(regionOption, data); replaceValuesFuncs(regionOption, data); removeFuncs(regionOption); options.args.region.args = regionOption; end function WeakAuras.PositionOptions(id, data, _, hideWidthHeight, disableSelfPoint) local metaOrder = 99 local function IsParentDynamicGroup() return data.parent and db.displays[data.parent] and db.displays[data.parent].regionType == "dynamicgroup"; end local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; local positionOptions = { __title = L["Position Settings"], __order = metaOrder, width = { type = "range", width = WeakAuras.normalWidth, name = L["Width"], order = 60, min = 1, softMax = screenWidth, bigStep = 1, hidden = hideWidthHeight, }, height = { type = "range", width = WeakAuras.normalWidth, name = L["Height"], order = 61, min = 1, softMax = screenHeight, bigStep = 1, hidden = hideWidthHeight, }, xOffset = { type = "range", width = WeakAuras.normalWidth, name = L["X Offset"], order = 62, softMin = (-1 * screenWidth), softMax = screenWidth, bigStep = 10, get = function() return data.xOffset end, set = function(info, v) data.xOffset = v; WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.ResetMoverSizer(); if(data.parent) then local parentData = WeakAuras.GetData(data.parent); if(parentData) then WeakAuras.Add(parentData); end end end }, yOffset = { type = "range", width = WeakAuras.normalWidth, name = L["Y Offset"], order = 63, softMin = (-1 * screenHeight), softMax = screenHeight, bigStep = 10, get = function() return data.yOffset end, set = function(info, v) data.yOffset = v; WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); WeakAuras.ResetMoverSizer(); if(data.parent) then local parentData = WeakAuras.GetData(data.parent); if(parentData) then WeakAuras.Add(parentData); end end end }, selfPoint = { type = "select", width = WeakAuras.normalWidth, name = L["Anchor"], order = 70, hidden = IsParentDynamicGroup, values = point_types, disabled = disableSelfPoint, }, anchorFrameType = { type = "select", width = WeakAuras.normalWidth, name = L["Anchored To"], order = 72, hidden = IsParentDynamicGroup, values = (data.regionType == "group" or data.regionType == "dynamicgroup") and WeakAuras.anchor_frame_types_group or WeakAuras.anchor_frame_types, }, -- Input field to select frame to anchor on anchorFrameFrame = { type = "input", width = WeakAuras.normalWidth, name = L["Frame"], order = 72.2, hidden = function() if (IsParentDynamicGroup()) then return true; end return not (data.anchorFrameType == "SELECTFRAME") end }, -- Button to select frame to anchor on chooseAnchorFrameFrame = { type = "execute", width = WeakAuras.normalWidth, name = L["Choose"], order = 72.4, hidden = function() if (IsParentDynamicGroup()) then return true; end return not (data.anchorFrameType == "SELECTFRAME") end, func = function() WeakAuras.StartFrameChooser(data, {"anchorFrameFrame"}); end }, anchorPoint = { type = "select", width = WeakAuras.normalWidth, name = function() if (data.anchorFrameType == "SCREEN") then return L["To Screen's"] else return L["To Frame's"]; end end, order = 75, hidden = function() if (data.parent) then --if (IsParentDynamicGroup()) then -- return true; --end return data.anchorFrameType == "SCREEN" or data.anchorFrameType == "MOUSE"; else return data.anchorFrameType == "MOUSE"; end end, values = point_types }, anchorPointGroup = { type = "select", width = WeakAuras.normalWidth, name = L["To Group's"], order = 76, hidden = function() if (data.anchorFrameType ~= "SCREEN") then return true; end if (data.parent) then return IsParentDynamicGroup(); end return true; end, disabled = true, values = {["CENTER"] = L["Anchor Point"]}, get = function() return "CENTER"; end }, anchorFrameParent = { type = "toggle", width = WeakAuras.normalWidth, name = L["Set Parent to Anchor"], desc = L["Sets the anchored frame as the aura's parent, causing the aura to inherit attributes such as visibility and scale."], order = 77, get = function() return data.anchorFrameParent or data.anchorFrameParent == nil; end, hidden = function() return (data.anchorFrameType == "SCREEN" or data.anchorFrameType == "MOUSE" or IsParentDynamicGroup()); end, }, frameStrata = { type = "select", width = WeakAuras.normalWidth, name = L["Frame Strata"], order = 78, values = WeakAuras.frame_strata_types }, anchorFrameSpace = { type = "execute", width = WeakAuras.normalWidth, name = "", order = 79, image = function() return "", 0, 0 end, hidden = function() return not (data.anchorFrameType ~= "SCREEN" or IsParentDynamicGroup()); end }, }; WeakAuras.AddCodeOption(positionOptions, data, L["Custom Anchor"], "custom_anchor", "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#custom-anchor-function", 72.1, function() return not(data.anchorFrameType == "CUSTOM" and not IsParentDynamicGroup()) end, {"customAnchor"}, nil, nil, nil, nil, nil, true) return positionOptions; end -- TODO: update this function to not have an unused parameter function WeakAuras.BorderOptions(id, data, showBackDropOptions, hiddenFunc, order) local borderOptions = { borderHeader = { type = "header", order = order, name = L["Border Settings"], hidden = hiddenFunc, }, border = { type = "toggle", width = WeakAuras.doubleWidth, name = L["Show Border"], order = order + 0.1, hidden = hiddenFunc, }, borderEdge = { type = "select", width = WeakAuras.normalWidth, dialogControl = "LSM30_Border", name = L["Border Style"], order = order + 0.2, values = AceGUIWidgetLSMlists.border, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, borderBackdrop = { type = "select", width = WeakAuras.normalWidth, dialogControl = "LSM30_Background", name = L["Backdrop Style"], order = order + 0.3, values = AceGUIWidgetLSMlists.background, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, borderOffset = { type = "range", width = WeakAuras.normalWidth, name = L["Border Offset"], order = order + 0.3, softMin = 0, softMax = 32, bigStep = 1, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, borderSize = { type = "range", width = WeakAuras.normalWidth, name = L["Border Size"], order = order + 0.4, softMin = 1, softMax = 64, bigStep = 1, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, borderInset = { type = "range", width = WeakAuras.normalWidth, name = L["Border Inset"], order = order + 0.5, softMin = 1, softMax = 32, bigStep = 1, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, border_spacer = { type = "description", name = "", width = WeakAuras.normalWidth, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, order = order + 0.6 }, borderColor = { type = "color", width = WeakAuras.normalWidth, name = L["Border Color"], hasAlpha = true, order = order + 0.7, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, borderInFront = { type = "toggle", width = WeakAuras.normalWidth, name = L["Border in Front"], order = order + 0.8, hidden = function() return hiddenFunc and hiddenFunc() or not data.border or not showBackDropOptions end, }, backdropColor = { type = "color", width = WeakAuras.normalWidth, name = L["Backdrop Color"], hasAlpha = true, order = order + 0.9, hidden = function() return hiddenFunc and hiddenFunc() or not data.border end, }, backdropInFront = { type = "toggle", width = WeakAuras.normalWidth, name = L["Backdrop in Front"], order = order + 1, hidden = function() return hiddenFunc and hiddenFunc() or not data.border or not showBackDropOptions end, }, } return borderOptions; end function WeakAuras.OpenTextEditor(...) frame.texteditor:Open(...); end function WeakAuras.ExportToString(id) frame.importexport:Open("export", id); end function WeakAuras.ExportToTable(id) frame.importexport:Open("table", id); end function WeakAuras.ImportFromString() frame.importexport:Open("import"); end function WeakAuras.CloseImportExport() frame.codereview:Close(); frame.importexport:Close(); end function WeakAuras.ConvertDisplay(data, newType) local id = data.id; local visibility = displayButtons[id]:GetVisibility(); displayButtons[id]:PriorityHide(0); WeakAuras.regions[id].region:Collapse(); WeakAuras.CollapseAllClones(id); WeakAuras.Convert(data, newType); displayButtons[id]:SetViewRegion(WeakAuras.regions[id].region); displayButtons[id]:Initialize(); displayButtons[id]:PriorityShow(visibility); displayOptions[id] = nil; WeakAuras.AddOption(id, data); frame:FillOptions(displayOptions[id]); WeakAuras.UpdateDisplayButton(data); WeakAuras.SetMoverSizer(id) WeakAuras.ResetMoverSizer(); WeakAuras.SortDisplayButtons() end function WeakAuras.NewDisplayButton(data) local id = data.id; WeakAuras.ScanForLoads({[id] = true}); WeakAuras.EnsureDisplayButton(db.displays[id]); WeakAuras.UpdateDisplayButton(db.displays[id]); frame.buttonsScroll:AddChild(displayButtons[id]); WeakAuras.AddOption(id, data); WeakAuras.SetIconNames(data); WeakAuras.SortDisplayButtons(); end function WeakAuras.UpdateGroupOrders(data) if(data.controlledChildren) then local total = #data.controlledChildren; for index, id in pairs(data.controlledChildren) do local button = WeakAuras.GetDisplayButton(id); button:SetGroupOrder(index, total); end end end function WeakAuras.UpdateButtonsScroll() if WeakAuras.IsOptionsProcessingPaused() then return end frame.buttonsScroll:DoLayout() end local previousFilter; function WeakAuras.SortDisplayButtons(filter, overrideReset, id) if (WeakAuras.IsOptionsProcessingPaused()) then return; end local recenter = false; filter = filter or (overrideReset and previousFilter or ""); if(frame.filterInput:GetText() ~= filter) then frame.filterInput:SetText(filter); end if(previousFilter and previousFilter ~= "" and (filter == "" or not filter)) then recenter = true; end previousFilter = filter; filter = filter:lower(); wipe(frame.buttonsScroll.children); --tinsert(frame.buttonsScroll.children, frame.newButton); --if(frame.addonsButton) then -- tinsert(frame.buttonsScroll.children, frame.addonsButton); --end tinsert(frame.buttonsScroll.children, frame.loadedButton); local numLoaded = 0; local to_sort = {}; local children = {}; local containsFilter = false; local visible = {} for id, child in pairs(displayButtons) do containsFilter = not filter or filter == ""; local data = WeakAuras.GetData(id); if not(data) then print("|cFF8800FFWeakAuras|r: No data for", id); else if(not containsFilter and data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do if(childId:lower():find(filter, 1, true)) then containsFilter = true; break; end end end if( frame.loadedButton:GetExpanded() and (not filter or id:lower():find(filter, 1, true) or containsFilter) ) then local group = child:GetGroup(); if(group) then -- In a Group if(loaded[group]) then if(loaded[id]) then child:EnableLoaded(); else child:DisableLoaded(); end children[group] = children[group] or {}; visible[id] = true tinsert(children[group], id); end else -- Top Level if(loaded[id] ~= nil) then if(loaded[id]) then child:EnableLoaded(); else child:DisableLoaded(); end visible[id] = true tinsert(to_sort, child); end end end end end table.sort(to_sort, function(a, b) return a:GetTitle() < b:GetTitle() end); for _, child in ipairs(to_sort) do child.frame:Show(); if child.AcquireThumbnail then child:AcquireThumbnail() end tinsert(frame.buttonsScroll.children, child); local controlledChildren = children[child:GetTitle()]; if(controlledChildren) then table.sort(controlledChildren, function(a, b) return displayButtons[a]:GetGroupOrder() < displayButtons[b]:GetGroupOrder(); end); for _, groupchild in ipairs(controlledChildren) do if(child:GetExpanded() and visible[groupchild]) then displayButtons[groupchild].frame:Show(); if displayButtons[groupchild].AcquireThumbnail then displayButtons[groupchild]:AcquireThumbnail() end tinsert(frame.buttonsScroll.children, displayButtons[groupchild]); end end end end -- Now handle unloaded auras tinsert(frame.buttonsScroll.children, frame.unloadedButton); local numUnloaded = 0; wipe(to_sort); wipe(children); for id, child in pairs(displayButtons) do containsFilter = not filter or filter == ""; local data = WeakAuras.GetData(id); if(not containsFilter and data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do if(childId:lower():find(filter, 1, true)) then containsFilter = true; break; end end end if( frame.unloadedButton:GetExpanded() and (not filter or id:lower():find(filter, 1, true) or containsFilter) ) then local group = child:GetGroup(); if(group) then if not(loaded[group]) then if(loaded[id]) then child:EnableLoaded(); else child:DisableLoaded(); end children[group] = children[group] or {}; visible[id] = true tinsert(children[group], id); end else if(loaded[id] == nil) then child:DisableLoaded(); visible[id] = true tinsert(to_sort, child); end end end end table.sort(to_sort, function(a, b) return a:GetTitle() < b:GetTitle() end); for _, child in ipairs(to_sort) do child.frame:Show(); if child.AcquireThumbnail then child:AcquireThumbnail() end tinsert(frame.buttonsScroll.children, child); local controlledChildren = children[child:GetTitle()]; if(controlledChildren) then table.sort(controlledChildren, function(a, b) return displayButtons[a]:GetGroupOrder() < displayButtons[b]:GetGroupOrder(); end); for _, groupchild in ipairs(controlledChildren) do if(child:GetExpanded() and visible[groupchild]) then displayButtons[groupchild].frame:Show(); if displayButtons[groupchild].AcquireThumbnail then displayButtons[groupchild]:AcquireThumbnail() end tinsert(frame.buttonsScroll.children, displayButtons[groupchild]); end end end end -- Hiding the other buttons for id, child in pairs(displayButtons) do local group = child:GetGroup(); local groupVisible = not group or visible[group] and displayButtons[group]:GetExpanded() if(not groupVisible or not visible[id]) then child.frame:Hide(); if child.ReleaseThumbnail then child:ReleaseThumbnail() end end end frame.buttonsScroll:DoLayout(); if(recenter) then frame:CenterOnPicked(); end end WeakAuras.afterScanForLoads = function() if(frame) then if (frame:IsVisible()) then WeakAuras.SortDisplayButtons(nil, true); else frame.needsSort = true; end end end function WeakAuras.IsPickedMultiple() if(frame.pickedDisplay == tempGroup) then return true; else return false; end end function WeakAuras.IsDisplayPicked(id) if(frame.pickedDisplay == tempGroup) then for index, childId in pairs(tempGroup.controlledChildren) do if(id == childId) then return true; end end return false; else return frame.pickedDisplay == id; end end function WeakAuras.PickDisplay(id, tab, noHide) -- TODO: remove tab parametter once legacy aura trigger is removed frame:PickDisplay(id, tab, noHide) WeakAuras.UpdateButtonsScroll() end function WeakAuras.PickAndEditDisplay(id) frame:PickDisplay(id); displayButtons[id].callbacks.OnRenameClick(); WeakAuras.UpdateButtonsScroll() end function WeakAuras.ClearPick(id) frame:ClearPick(id); end function WeakAuras.ClearPicks() frame:ClearPicks(); end function WeakAuras.PickDisplayMultiple(id) frame:PickDisplayMultiple(id); end function WeakAuras.PickDisplayMultipleShift(target) if (frame.pickedDisplay) then -- get first aura selected local first; if (WeakAuras.IsPickedMultiple()) then first = tempGroup.controlledChildren[#tempGroup.controlledChildren]; else first = frame.pickedDisplay; end if (first and first ~= target) then -- check if target and first are in same group and are not a group local firstData = WeakAuras.GetData(first); local targetData = WeakAuras.GetData(target); if (firstData.parent == targetData.parent and not targetData.controlledChildren and not firstData.controlledChildren) then local batchSelection = {}; -- in a group if (firstData.parent) then local group = WeakAuras.GetData(targetData.parent); for index, child in ipairs(group.controlledChildren) do -- 1st button if (child == target or child == first) then table.insert(batchSelection, child); for i = index + 1, #group.controlledChildren do local current = group.controlledChildren[i]; table.insert(batchSelection, current); -- last button: stop selection if (current == target or current == first) then break; end end break; end end elseif (firstData.parent == nil and targetData.parent == nil) then -- top-level for index, button in ipairs(frame.buttonsScroll.children) do local data = button.data; -- 1st button if (data and (data.id == target or data.id == first)) then table.insert(batchSelection, data.id); for i = index + 1, #frame.buttonsScroll.children do local current = frame.buttonsScroll.children[i]; local currentData = current.data; if currentData and not currentData.parent and not currentData.controlledChildren then table.insert(batchSelection, currentData.id); -- last button: stop selection if (currentData.id == target or currentData.id == first) then break; end end end break; end end end if #batchSelection > 0 then frame:PickDisplayBatch(batchSelection); end end end else WeakAuras.PickDisplay(target); end end function WeakAuras.GetDisplayButton(id) if(id and displayButtons[id]) then return displayButtons[id]; end end function WeakAuras.AddDisplayButton(data) WeakAuras.EnsureDisplayButton(data); WeakAuras.UpdateDisplayButton(data); frame.buttonsScroll:AddChild(displayButtons[data.id]); WeakAuras.AddOption(data.id, data); WeakAuras.SetIconNames(data); if(WeakAuras.regions[data.id] and WeakAuras.regions[data.id].region.SetStacks) then WeakAuras.regions[data.id].region:SetStacks(1); end end function WeakAuras.EnsureDisplayButton(data) local id = data.id; if not(displayButtons[id]) then displayButtons[id] = AceGUI:Create("WeakAurasDisplayButton"); if(displayButtons[id]) then displayButtons[id]:SetData(data); displayButtons[id]:Initialize(); else print("|cFF8800FFWeakAuras|r: Error creating button for", id); end end end function WeakAuras.SetGrouping(data) if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0 and data) then local children = {}; -- set grouping for selected buttons for index, childId in ipairs(tempGroup.controlledChildren) do local button = WeakAuras.GetDisplayButton(childId); button:SetGrouping(tempGroup.controlledChildren, true); children[childId] = true; end -- set grouping for non selected buttons for id, button in pairs(displayButtons) do if not children[button.data.id] then button:SetGrouping(tempGroup.controlledChildren); end end else for id, button in pairs(displayButtons) do button:SetGrouping(data); end end end function WeakAuras.Ungroup(data) if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0) then for index, childId in ipairs(tempGroup.controlledChildren) do local button = WeakAuras.GetDisplayButton(childId); button:Ungroup(data); end else local button = WeakAuras.GetDisplayButton(data.id); button:Ungroup(data); end end function WeakAuras.SetDragging(data, drop) WeakAuras_DropDownMenu:Hide() if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0) then local children = {}; local size = #tempGroup.controlledChildren; -- set dragging for selected buttons in reverse for ordering for index = size, 1, -1 do local childId = tempGroup.controlledChildren[index]; local button = WeakAuras.GetDisplayButton(childId); button:SetDragging(data, drop, size); children[childId] = true; end -- set dragging for non selected buttons for id, button in pairs(displayButtons) do if not children[button.data.id] then button:SetDragging(data, drop); end end else for id, button in pairs(displayButtons) do button:SetDragging(data, drop); end end end function WeakAuras.DropIndicator() local indicator = frame.dropIndicator if not indicator then indicator = CreateFrame("Frame", "WeakAuras_DropIndicator") indicator:SetHeight(4) indicator:SetFrameStrata("FULLSCREEN") local texture = indicator:CreateTexture(nil, "FULLSCREEN") texture:SetBlendMode("ADD") texture:SetAllPoints(indicator) texture:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight") local icon = indicator:CreateTexture(nil, "OVERLAY") icon:SetSize(16,16) icon:SetPoint("CENTER", indicator) indicator.icon = icon indicator.texture = texture frame.dropIndicator = indicator indicator:Hide() end return indicator end function WeakAuras.UpdateDisplayButton(data) local id = data.id; local button = displayButtons[id]; if (button) then button:UpdateThumbnail() if WeakAurasCompanion and button:IsGroup() then button:RefreshUpdate() end -- TODO: remove this once legacy aura trigger is removed button:RefreshBT2UpgradeIcon() end end function WeakAuras.UpdateThumbnail(data) local id = data.id local button = displayButtons[id] if (not button) then return end button:UpdateThumbnail() end function WeakAuras.OpenTexturePicker(data, field, textures, stopMotion) frame.texturePicker:Open(data, field, textures, stopMotion); end function WeakAuras.OpenIconPicker(data, field, groupIcon) frame.iconPicker:Open(data, field, groupIcon); end function WeakAuras.OpenModelPicker(data, field, parentData) if not(IsAddOnLoaded("WeakAurasModelPaths")) then local loaded, reason = LoadAddOn("WeakAurasModelPaths"); if not(loaded) then reason = string.lower("|cffff2020" .. _G["ADDON_" .. reason] .. "|r.") print(WeakAuras.printPrefix .. "ModelPaths could not be loaded, the addon is " .. reason); WeakAuras.ModelPaths = {}; end frame.modelPicker.modelTree:SetTree(WeakAuras.ModelPaths); end frame.modelPicker:Open(data, field, parentData); end function WeakAuras.OpenCodeReview(data) frame.codereview:Open(data); end function WeakAuras.CloseCodeReview(data) frame.codereview:Close(); end function WeakAuras.OpenTriggerTemplate(data, targetId) if not(IsAddOnLoaded("WeakAurasTemplates")) then local loaded, reason = LoadAddOn("WeakAurasTemplates"); if not(loaded) then reason = string.lower("|cffff2020" .. _G["ADDON_" .. reason] .. "|r.") print(WeakAuras.printPrefix .. "Templates could not be loaded, the addon is " .. reason); return; end frame.newView = WeakAuras.CreateTemplateView(frame); end frame.newView.targetId = targetId; frame.newView:Open(data); end function WeakAuras.ResetMoverSizer() if(frame and frame.mover and frame.moversizer and frame.mover.moving.region and frame.mover.moving.data) then frame.moversizer:SetToRegion(frame.mover.moving.region, frame.mover.moving.data); end end function WeakAuras.SetMoverSizer(id) if WeakAuras.regions[id].region.toShow then frame.moversizer:SetToRegion(WeakAuras.regions[id].region, db.displays[id]) else if WeakAuras.clones[id] then local cloneId, clone = next(WeakAuras.clones[id]) if clone then frame.moversizer:SetToRegion(clone, db.displays[id]) end end end end function WeakAuras.GetMoverSizerId() return frame.moversizer:GetCurrentId() end function WeakAuras.ShowCloneDialog(data) if( not( data.parent and WeakAuras.GetData(data.parent) and WeakAuras.GetData(data.parent).regionType == "dynamicgroup" ) and not(odb.preventCloneDialog) ) then StaticPopupDialogs["WEAKAURAS_CLONE_OPTION_ENABLED"] = { text = L["Clone option enabled dialog"], button1 = L["Yes"], button2 = L["No"], button3 = L["Never"], OnAccept = function() local parentData = { id = WeakAuras.FindUnusedId(data.id.." Group"), regionType = "dynamicgroup", }; WeakAuras.Add(parentData); WeakAuras.NewDisplayButton(parentData); tinsert(parentData.controlledChildren, data.id); data.parent = parentData.id; WeakAuras.Add(parentData); WeakAuras.Add(data); local button = WeakAuras.GetDisplayButton(data.id); button:SetGroup(parentData.id, true); button:SetGroupOrder(1, #parentData.controlledChildren); local parentButton = WeakAuras.GetDisplayButton(parentData.id); parentButton.callbacks.UpdateExpandButton(); WeakAuras.UpdateDisplayButton(parentData); WeakAuras.ReloadGroupRegionOptions(parentData); WeakAuras.SortDisplayButtons(); parentButton:Expand(); end, OnCancel = function() -- do nothing end, OnAlt = function() odb.preventCloneDialog = true end, hideOnEscape = true, whileDead = true, timeout = 0, preferredindex = STATICPOPUP_NUMDIALOGS }; StaticPopup_Show("WEAKAURAS_CLONE_OPTION_ENABLED"); end end local function AddDefaultSubRegions(data) data.subRegions = data.subRegions or {} for type, subRegionData in pairs(WeakAuras.subRegionTypes) do if subRegionData.addDefaultsForNewAura then subRegionData.addDefaultsForNewAura(data) end end end function WeakAuras.NewAura(sourceData, regionType, targetId) local function ensure(t, k, v) return t and k and v and t[k] == v end local new_id = WeakAuras.FindUnusedId("New") local data = {id = new_id, regionType = regionType, uid = WeakAuras.GenerateUniqueID()} WeakAuras.DeepCopy(WeakAuras.data_stub, data); if (sourceData) then WeakAuras.DeepCopy(sourceData, data); end data.internalVersion = WeakAuras.InternalVersion(); WeakAuras.validate(data, WeakAuras.regionTypes[regionType].default); AddDefaultSubRegions(data) if (data.regionType ~= "group" and data.regionType ~= "dynamicgroup" and targetId) then local target = WeakAuras.GetDisplayButton(targetId); local group if (target) then if (target:IsGroup()) then group = target; else group = WeakAuras.GetDisplayButton(target.data.parent); end if (group) then local children = group.data.controlledChildren; local index = target:GetGroupOrder(); if (ensure(children, index, target.data.id)) then -- account for insert position index = index + 1; tinsert(children, index, data.id); else -- move source into group as the first child tinsert(children, 1, data.id); end data.parent = group.data.id; WeakAuras.Add(data); WeakAuras.Add(group.data); WeakAuras.NewDisplayButton(data); WeakAuras.UpdateGroupOrders(group.data); WeakAuras.ReloadGroupRegionOptions(group.data); WeakAuras.UpdateDisplayButton(group.data); group.callbacks.UpdateExpandButton(); group:Expand(); group:ReloadTooltip(); WeakAuras.PickAndEditDisplay(data.id); else -- move source into the top-level list WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); WeakAuras.PickAndEditDisplay(data.id); end else error("Calling 'WeakAuras.NewAura' with invalid groupId. Reload your UI to fix the display list.") end else -- move source into the top-level list WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); WeakAuras.PickAndEditDisplay(data.id); end end local collapsedOptions = {} local collapsed = {} -- magic value WeakAuras.collapsedOptions = collapsedOptions function WeakAuras.ResetCollapsed(id, namespace) if id then if namespace and collapsedOptions[id] then collapsedOptions[id][namespace] = nil else collapsedOptions[id] = nil end end end function WeakAuras.IsCollapsed(id, namespace, path, default) local tmp = collapsedOptions[id] if tmp == nil then return default end tmp = tmp[namespace] if tmp == nil then return default end if type(path) ~= "table" then tmp = tmp[path] else for _, key in ipairs(path) do tmp = tmp[key] if tmp == nil or tmp[collapsed] then break end end end if tmp == nil or tmp[collapsed] == nil then return default else return tmp[collapsed] end end function WeakAuras.SetCollapsed(id, namespace, path, v) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then collapsedOptions[id][namespace][path] = collapsedOptions[id][namespace][path] or {} collapsedOptions[id][namespace][path][collapsed] = v else local tmp = collapsedOptions[id][namespace] or {} for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end tmp[collapsed] = v end end function WeakAuras.MoveCollapseDataUp(id, namespace, path) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then collapsedOptions[id][namespace][path], collapsedOptions[id][namespace][path - 1] = collapsedOptions[id][namespace][path - 1], collapsedOptions[id][namespace][path] else local tmp = collapsedOptions[id][namespace] local lastKey = tremove(path) for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end tmp[lastKey], tmp[lastKey - 1] = tmp[lastKey - 1], tmp[lastKey] end end function WeakAuras.MoveCollapseDataDown(id, namespace, path) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then collapsedOptions[id][namespace][path], collapsedOptions[id][namespace][path + 1] = collapsedOptions[id][namespace][path + 1], collapsedOptions[id][namespace][path] else local tmp = collapsedOptions[id][namespace] local lastKey = tremove(path) for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end tmp[lastKey], tmp[lastKey + 1] = tmp[lastKey + 1], tmp[lastKey] end end function WeakAuras.RemoveCollapsed(id, namespace, path) local data = collapsedOptions[id] and collapsedOptions[id][namespace] if not data then return end local index local maxIndex = 0 if type(path) ~= "table" then index = path else index = path[#path] for i = 1, #path - 1 do data = data[path[i]] if not data then return end end end for k in pairs(data) do if k ~= collapsed then maxIndex = max(maxIndex, k) end end while index <= maxIndex do data[index] = data[index + 1] index = index + 1 end end function WeakAuras.InsertCollapsed(id, namespace, path, value) local data = collapsedOptions[id] and collapsedOptions[id][namespace] if not data then return end local insertPoint local maxIndex if type(path) ~= "table" then insertPoint = path else insertPoint = path[#path] for i = 1, #path - 1 do data = data[path[i]] if not data then return end end end for k in pairs(data) do if k ~= collapsed and k >= insertPoint then if not maxIndex or k > maxIndex then maxIndex = k end end end if maxIndex then -- may be nil if insertPoint is greater than the max of anything else for i = maxIndex, insertPoint, -1 do data[i + 1] = data[i] end end data[insertPoint] = {[collapsed] = value} end function WeakAuras.RenameCollapsedData(oldid, newid) collapsedOptions[newid] = collapsedOptions[oldid] collapsedOptions[oldid] = nil end function WeakAuras.DeleteCollapsedData(id) collapsedOptions[id] = nil end