-- read namespace from global env local _G = _G local Grichelde = _G.Grichelde or {} local assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tInsert, tClone, tWipe, find, match, cPrefix, cGray, cGreen, cOrange, cRed, format, toString, toNumber = Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tInsert, Grichelde.F.tClone, Grichelde.F.tWipe, Grichelde.F.find, Grichelde.F.match, Grichelde.F.cPrefix, Grichelde.F.cGray, Grichelde.F.cGreen, Grichelde.F.cOrange, Grichelde.F.cRed, Grichelde.F.format, Grichelde.F.toString, Grichelde.F.toNumber local selectedExample = 1 function Grichelde:CreateOptionsUI() return { name = self:Format(self.L.Options_Title, self.L.AddonName), type = "group", childGroups = "tab", set = function(info, val) self:SyncToDatabase(info, val) end, get = function(info) return self:ReadFromDatabase(info) end, hidden = false, disabled = function(info) return self:IsDisabled(info) end, args = { enabled = { order = 0, type = "toggle", name = self.L.Options_Enabled_Name, desc = self:Format(self.L.Options_Enabled_Desc, self.L.AddonName), set = function() self:ToggleActivation() end, disabled = false, }, minimapButton = { order = 1, type = "toggle", name = self.L.Options_Minimap_Button_Name, desc = self.L.Options_Minimap_Button_Desc, set = function(_, _) self:ToggleMinimapButton() end, get = function(_) return not self.db.profile.minimapButton.hide end, disabled = false, }, replacements = { order = 2, type = "group", name = self.L.Options_Replacements_Group_Name, desc = self.L.Options_Replacements_Group_Desc, args = { add = { order = 0, type = "execute", confirm = false, name = self.L.Options_Replacements_Add_Name, desc = self.L.Options_Replacements_Add_Desc, func = function(info) self:AddEmptyMapping(info) end, }, deleteAll = { order = 1, type = "execute", confirm = true, confirmText = self.L.Options_Replacements_DeleteAll_ConfirmText, name = self.L.Options_Replacements_DeleteAll_Name, desc = self.L.Options_Replacements_DeleteAll_Desc, func = function(info) self:DeleteAllMappings(info) end, }, header = { order = 3, type = "description", name = self.L.Options_Replacements_Header, }, spacer1 = { order = 4, type = "header", name = "", }, }, }, channels = { order = 3, type = "group", name = self.L.Options_Channels_Group_Name, desc = self:Format(self.L.Options_Channels_Group_Desc, self.L.AddonName), args = { header = { order = 1, type = "description", name = self.L.Options_Channels_Header, }, spacer2 = { order = 2, type = "header", name = "", }, say = { order = 10, type = "toggle", name = self.L.Options_Channel_Say_Name, desc = self:Format(self.L.Options_Channel_Say_Desc, self.L.AddonName), }, emote = { order = 11, type = "toggle", name = self.L.Options_Channel_Emote_Name, desc = self:Format(self.L.Options_Channel_Emote_Desc, self.L.AddonName), }, yell = { order = 12, type = "toggle", name = self.L.Options_Channel_Yell_Name, desc = self:Format(self.L.Options_Channel_Yell_Desc, self.L.AddonName), }, party = { order = 13, type = "toggle", name = self.L.Options_Channel_Party_Name, desc = self:Format(self.L.Options_Channel_Party_Desc, self.L.AddonName), }, guild = { order = 14, type = "toggle", name = self.L.Options_Channel_Guild_Name, desc = self:Format(self.L.Options_Channel_Guild_Desc, self.L.AddonName), }, officer = { order = 15, type = "toggle", name = self.L.Options_Channel_Officer_Name, desc = self:Format(self.L.Options_Channel_Officer_Desc, self.L.AddonName), }, raid = { order = 16, type = "toggle", name = self.L.Options_Channel_Raid_Name, desc = self:Format(self.L.Options_Channel_Raid_Desc, self.L.AddonName), }, raidWarning = { order = 17, type = "toggle", name = self.L.Options_Channel_RaidWarning_Name, desc = self:Format(self.L.Options_Channel_RaidWarning_Desc, self.L.AddonName), }, instance = { order = 18, type = "toggle", name = self.L.Options_Channel_Instance_Name, desc = self:Format(self.L.Options_Channel_Instance_Desc, self.L.AddonName), }, battleground = { order = 19, type = "toggle", name = self.L.Options_Channel_Battleground_Name, desc = self:Format(self.L.Options_Channel_Battleground_Desc, self.L.AddonName), }, whisper = { order = 20, type = "toggle", name = self.L.Options_Channel_Whisper_Name, desc = self:Format(self.L.Options_Channel_Whisper_Desc, self.L.AddonName), }, }, }, help = { order = -1, type = "group", childGroups = "tab", name = self.L.Options_Help_Group_Name, desc = self.L.Options_Help_Group_Desc, disabled = false, args = { info = { order = 10, type = "group", name = self.L.Options_Help_Tab_Info_Name, desc = self.L.Options_Help_Tab_Info_Desc, args = { paragraph1 = { order = 1, type = "description", name = self:Format(self.L.Options_Help_Info, self.L.AddonName), fontSize = "medium", }, }, }, basics = { order = 11, type = "group", name = self.L.Options_Help_Tab_Basics_Name, desc = self.L.Options_Help_Tab_Basics_Desc, args = { paragraph1 = { order = 1, type = "description", name = self.L.Options_Help_Basics, fontSize = "medium", }, }, }, expert = { order = 12, type = "group", name = self.L.Options_Help_Tab_Expert_Name, desc = self.L.Options_Help_Tab_Expert_Desc, args = { paragraph4 = { order = 1, type = "description", name = self.L.Options_Help_Expert, fontSize = "medium", }, }, }, examples = { order = 13, type = "group", name = self.L.Options_Help_Tab_Examples_Name, desc = self.L.Options_Help_Tab_Examples_Desc, args = { note = { order = 1, type = "description", name = self.L.Options_Help_Examples_Note, }, header = { order = 2, type = "description", name = self.L.Options_Help_Examples_Header, fontSize = "medium", width = 2.1 }, dropDown = { order = 3, type = "select", name = "", width = 1.2, values = function(_) return self:ExtractExampleNames() end, set = function(_, val) selectedExample = val end, get = function(_) self.options.args.help.args.examples.args.header.name = self.L.Options_Help_Examples[selectedExample].desc self.options.args.help.args.examples.args.example.name = self:ExtractExampleCodes(selectedExample), self.dialog:SelectGroup(self.name, "help", "examples", "header.name") return selectedExample end, }, spacer3 = { order = 3, type = "description", name = "", desc = "", width = 2.3, }, import = { order = 4, type = "execute", width = 1, confirm = selectedExample > 0, name = self.L.Options_Help_Examples_Import_Name, -- desc = self.L.Options_Help_Examples_Import_Desc, desc = function() return format(self.L.Options_Help_Examples_Import_Desc, cPrefix(self.L.Options_Help_Examples[selectedExample].name)) end, func = function(_) self:ImportExample(selectedExample) end, }, spacer4 = { order = 6, type = "header", name = "", }, example = { order = 7, type = "description", name = self.L.Options_Help_Examples_Text, fontSize = "medium", }, }, }, disclaimer = { order = 14, type = "description", name = self.L.Options_Help_Disclaimer, }, }, }, }, } end function Grichelde:CreateMapping(offset) return { order = offset or 9999, type = "group", name = function(info) return self:MappingName(info) end, desc = self.L.Options_Mapping_Group_Desc, childGroups = "tree", disabled = function(info) return not self:IsMappingActive(info) end, args = { moveUp = { order = 0, type = "execute", name = "", desc = self.L.Options_Mapping_MoveUp_Desc, image = function(info) return self:GetMoveUpImage(info) end, width = 0.15, func = function(info) self:MoveUp(info) end, }, moveDown = { order = 1, type = "execute", name = "", desc = self.L.Options_Mapping_MoveDown_Desc, image = function(info) return self:GetMoveDownImage(info) end, width = 0.15, func = function(info) self:MoveDown(info) end, }, spacer5 = { order = 2, type = "description", name = "", desc = "", width = 0.05, }, --[[ active = { order = 3, type = "toggle", name = self.L.Options_Mapping_Enabled_Name, desc = self.L.Options_Mapping_Enabled_Desc, width = 2.2, }, ]] matchWhenLabel = { order = 3, type = "description", name = self.L.Options_Mapping_MatchWhen_Name, desc = self.L.Options_Mapping_MatchWhen_Desc, fontSize = "medium", width = 0.3, }, matchWhen = { order = 4, type = "select", name = " ", desc = self.L.Options_Mapping_MatchWhen_Desc, values = { self.L.Options_Mapping_MatchWhen_Select1, self.L.Options_Mapping_MatchWhen_Select2, self.L.Options_Mapping_MatchWhen_Select3, self.L.Options_Mapping_MatchWhen_Select4, self.L.Options_Mapping_MatchWhen_Select5, self.L.Options_Mapping_MatchWhen_Select6, }, width = 1.4, }, spacer6 = { order = 5, type = "description", name = "", desc = "", width = 0.1, }, delete = { order = 6, type = "execute", confirm = true, confirmText = self.L.Options_Mapping_Delete_ConfirmText, name = "", desc = self.L.Options_Mapping_Delete_Desc, image = function(info) return self:GetDeleteImage(info) end, width = 0.1, func = function(info) self:DeleteMapping(info) end, }, searchText = { order = 10, type = "input", name = self.L.Options_Mapping_SearchText_Name, desc = self.L.Options_Mapping_SearchText_Desc, }, replaceText = { order = 11, type = "input", name = self.L.Options_Mapping_ReplaceText_Name, desc = self.L.Options_Mapping_ReplaceText_Desc, }, exactCase = { order = 21, type = "toggle", name = self.L.Options_Mapping_ExactCase_Name, desc = self.L.Options_Mapping_ExactCase_Desc, width = "full", }, consolidate = { order = 22, type = "toggle", name = self.L.Options_Mapping_Consolidate_Name, desc = self.L.Options_Mapping_Consolidate_Desc, width = "full", }, stopOnMatch = { order = 23, type = "toggle", name = self.L.Options_Mapping_StopOnMatch_Name, desc = self.L.Options_Mapping_StopOnMatch_Desc, width = "full", } } } end function Grichelde:SetupOptions() -- add DB-backed profiles to UI options local options = self:CreateOptionsUI(self) options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db) options.args.profiles.order = 8 options.args.profiles.disabled = false -- Adding options to blizzard frame LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, options) local dialog = LibStub("AceConfigDialog-3.0") dialog:AddToBlizOptions(self.name, self.L.AddonName) return options, dialog end function Grichelde:RefreshProfiles(event, _, profileName) local function replaceReplacements(replacements) -- do NOT set self.db.profile.replacements = {} it will break defaults tWipe(self.db.profile.replacements) -- copy over sorted replacements for replName, replTable in pairs(replacements) do self.db.profile.replacements[replName] = replTable end self:DebugPrint("RefreshProfiles : reorderReplacements : sorted table") self:DebugPrint(self.db.profile.replacements) end local function addEmptyMappingWithoutRefresh() self:DebugPrint("RefreshProfiles : addEmptyMappingWithoutRefresh") self.db.profile.replacements.replacement_10.order = 10 end self:DebugPrint("RefreshProfiles : event:", event) --- AceDB will call OnProfileShutdown, OnProfileChanged and OnNewProfile in this order if (event == "OnNewProfile") then addEmptyMappingWithoutRefresh() self:PrefixedPrint(self.L.Profiles_Created, cGreen(self.db:GetCurrentProfile())) elseif (event == "OnProfileChanged") then self:DebugPrint(self.L.Profiles_Loaded, cGreen(self.db:GetCurrentProfile())) elseif (event == "OnProfileDeleted") then self:PrefixedPrint(self.L.Profiles_Deleted, cRed(profileName)) elseif (event == "OnProfileCopied") then self:DebugPrint(self.L.Profiles_Copied, cOrange(profileName)) elseif (event == "OnProfileReset") then addEmptyMappingWithoutRefresh() self:PrefixedPrint(self.L.Profiles_Reset, cOrange(self.db:GetCurrentProfile())) else self:DebugPrint("Refreshing Profile %s on options change: %s", self.db:GetCurrentProfile(), event) end local repls = self:ReorderReplacements() replaceReplacements(repls) self:RefreshOptions(repls) self:RefreshDialog() self:RefreshMinimap() end function Grichelde:ToggleOptions() self:DebugPrint("ToggleOptions : options open: ", not not self.dialog.OpenFrames[self.name]) if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then self:CloseOptions() else self:OpenOptions() end end function Grichelde:OpenOptions() if (self.dialog ~= nil) then self.dialog:Open(self.name) local formatString = self.L.AddonLoaded if (self.db.profile.enabled == false) then formatString = self.L.AddonUnloaded end local namePlusVersion = self:Format(self.L.AddonNamePlusVersion, self.L.AddonName, self.version) local statusText = self:Format(formatString, namePlusVersion) self.dialog.OpenFrames[self.name]:SetStatusText(statusText) end end function Grichelde:CloseOptions() if (self.dialog ~= nil) then self.dialog:Close(self.name) end end --- If all replacements were disabled -- @return (boolean) function Grichelde:IsDisabled(info) if (info and (info.option.type == "group")) then return false end return not self.db.profile.enabled end --- If all replacements were disabled -- @return (boolean) function Grichelde:IsMappingActive(info) self:TracePrint("IsMappingActive : info") for i = 0, #info do self:TracePrint("IsMappingActive : info[%d] = %s", i, info[i]) end if (info and (info.option.type == "group")) then return true end if (self.db.profile.enabled == false) then return false end local replacements = self.db.profile.replacements or {} local currentName = info[2] local uiElem = info[3] self:DebugPrint("IsMappingActive : \"%s\"", currentName) self:DebugPrint(replacements[currentName]) if (tContains({"moveUp", "moveDown", "matchWhen", "delete"}, uiElem)) then return true else return replacements[currentName].matchWhen > 1 end end function Grichelde:MappingName(info) -- self:TracePrint("MappingName : info") -- self:TracePrint(info) local option = self.db.profile.replacements[info[2]] if (nilOrEmpty(option.searchText) and nilOrEmpty(option.replaceText)) then return cGray(self.L.Options_Mapping_EmptyMapping) else local name = self:Format(self.L.Options_Mapping_Group_Name, option.searchText or "", option.replaceText or "") if (option.matchWhen > 1) then return name else return cGray(name) end end end function Grichelde:ExtractExampleNames() local exampleNames = {} for _, example in ipairs(self.L.Options_Help_Examples) do tInsert(exampleNames, example.name) end return exampleNames end function Grichelde:ExtractExampleCodes(num) self:TracePrint("ExtractExampleCodes : number is: %d", num) if (self.L.Options_Help_Examples[num] == nil) or (#self.L.Options_Help_Examples < num) then self:DebugPrint("ExtractExampleCodes : invalid number: %d", num) return self.L.Options_Help_Examples_Text end local exampleCodes = "" for replName, replTable in spairs(self.L.Options_Help_Examples[num].replacements) do self:TracePrint("ExtractExampleCodes : replacement: %s", replName) self:TracePrint(replTable) if (replTable ~= nil) and (replTable.searchText ~= nil) then if (exampleCodes ~= "") then exampleCodes = exampleCodes .. "|n|n" end exampleCodes = exampleCodes .. cPrefix(format("%s => %s", replTable.searchText, replTable.replaceText)) end end return exampleCodes end function Grichelde:ImportExample(num) self:TracePrint("ImportExample : number is: %d", num) if (self.L.Options_Help_Examples[num] == nil) or (#self.L.Options_Help_Examples < num) then self:DebugPrint("ImportExample : invalid number: %d", num) end local profileName = self.L.Options_Help_Examples[num].name self:DebugPrint("ImportExample : profile name: %s", profileName) local allProfiles = self.db:GetProfiles() if (not tContains(allProfiles, profileName)) then -- create new profile if not exists self.db:SetProfile(profileName) assert(self.db:GetCurrentProfile() == profileName, "profile was not loaded") tWipe(self.db.profile.replacements) for replName, replTable in spairs(self.L.Options_Help_Examples[num].replacements) do self:TracePrint("ImportExample : replacement: %s", replName) self:TracePrint(replTable) if (replName ~= nil) and (replTable ~= nil) and (replTable.searchText ~= nil) then self.db.profile.replacements[replName] = tClone(replTable) end end self:RefreshProfiles("ImportExample" .. num) else self:ErrorPrint(self.L.Profiles_AlreadyExistsError, profileName) end end --- Create UI options for the given replacement table (from DB). --- Usually called with with self.db.profile.replacements -- @param replacementsTable function Grichelde:RefreshOptions(replacementsTable) self:TracePrint("RefreshOptions : DB table:") self:TracePrint(replacementsTable) if (self.options ~= nil) then -- remove all previous replacements from options (not DB), except header and buttons local replacements = self.options.args.replacements.args or {} for k, _ in pairs(replacements) do if (k and (find(k, "^replacement_") ~= nil)) then replacements[k] = nil end end for replName, _ in pairs(replacementsTable or {}) do local _, replNumber = self:SplitOnFirstMatch(replName, "_") replacements[replName] = self:CreateMapping(toNumber(replNumber)) end end -- self:TracePrint("RefreshOptions : UI options:") -- self:TracePrint(replacements) end function Grichelde:RefreshDialog() if (self.dialog ~= nil) then self.dialog:ConfigTableChanged(nil, self.name) end end function Grichelde:AddEmptyMapping() local replacements = self.db.profile.replacements or {} self:DebugPrint("AddEmptyMapping : old DB entries:") self:DebugPrint(replacements) local maxRepl = Grichelde.MAPPING_OFFSET - 1 for replName, _ in pairs(replacements) do local num = match(replName, "^replacement_(%d+)") if (num ~= nil) and (maxRepl < toNumber(num)) then maxRepl = toNumber(num) end end local newMapping = "replacement_" .. toString(maxRepl + 1) self:DebugPrint("AddEmptyMapping : new mapping key:", newMapping) -- do NOT set self.db.profile.replacements = {} it will break defaults replacements[newMapping].order = toString(maxRepl + 1) -- will be reordered anyway self:DebugPrint("AddEmptyMapping : new DB entries:") self:DebugPrint(replacements) self:RefreshProfiles("AddEmptyMapping " .. newMapping) self.dialog:SelectGroup(self.name, "replacements", newMapping) end function Grichelde:MoveUp(info) self:TracePrint("MoveUp : info") for i = 0, #info do self:TracePrint("%d = %s", i, info[i]) end local replacements = self.db.profile.replacements or {} local currentName = info[2] self:DebugPrint("MoveUp : \"%s\"", currentName) self:DebugPrint(replacements[currentName]) local _, replNumber = self:SplitOnFirstMatch(currentName, "_") local currentOrder = toNumber(replNumber) -- if not on top if (currentOrder ~= Grichelde.MAPPING_OFFSET) then local swapName = "replacement_" .. toString(currentOrder - 1) -- swap ordering self:DebugPrint("swap with option %s", swapName) replacements[swapName].order = currentOrder replacements[currentName].order = currentOrder - 1 self:RefreshProfiles("MoveUp " .. currentName) self:DebugPrint("MoveUp : refresh focus on %s", swapName) self.dialog:SelectGroup(self.name, "replacements", swapName) else self:DebugPrint("MoveUp : already on top") end end function Grichelde:MoveDown(info) self:TracePrint("MoveDown : info") for i = 0, #info do self:TracePrint("%d = %s", i, info[i]) end local replacements = self.db.profile.replacements or {} local currentName = info[2] self:DebugPrint("MoveDown : \"%s\"", currentName) self:DebugPrint(replacements[currentName]) local _, replNumber = self:SplitOnFirstMatch(currentName, "_") local currentOrder = toNumber(replNumber) local maxRepl = Grichelde.MAPPING_OFFSET for replName, _ in pairs(replacements) do local num = match(replName, "^replacement_(%d+)") if num and maxRepl < toNumber(num) then maxRepl = toNumber(num) end end -- if not last element self:DebugPrint("MoveDown : maxRepl: %d", maxRepl) if (currentOrder < maxRepl) then local swapName = "replacement_" .. toString(currentOrder + 1) -- swap ordering self:DebugPrint("swap with option %s", swapName) replacements[swapName].order = currentOrder replacements[currentName].order = currentOrder + 1 self:RefreshProfiles("MoveDown " .. currentName) self:DebugPrint("MoveDown : refresh focus on %s", swapName) self.dialog:SelectGroup(self.name, "replacements", swapName) else self:DebugPrint("MoveDown : already at bottom") end end function Grichelde:GetMoveUpImage(info) self:TracePrint("GetMoveUpImage : info") for i = 0, #info do self:TracePrint("%d = %s", i, info[i]) end local currentName = info[2] self:DebugPrint("GetMoveUpImage : \"%s\"", currentName) local _, replNumber = self:SplitOnFirstMatch(currentName, "_") local currentOrder = toNumber(replNumber) if (self:IsMappingActive(info) and (currentOrder > Grichelde.MAPPING_OFFSET)) then return Grichelde.ICONS.MOVE_UP else return Grichelde.ICONS.MOVE_UP_DISABLED end end function Grichelde:GetMoveDownImage(info) self:TracePrint("GetMoveDownImage : info") for i = 0, #info do self:TracePrint("%d = %s", i, info[i]) end local currentName = info[2] self:DebugPrint("GetMoveDownImage : \"%s\"", currentName) local _, replNumber = self:SplitOnFirstMatch(currentName, "_") local currentOrder = toNumber(replNumber) local maxRepl = Grichelde.MAPPING_OFFSET local replacements = self.db.profile.replacements or {} for replName, _ in pairs(replacements) do local num = match(replName, "^replacement_(%d+)") if (num ~= nil) and (maxRepl < toNumber(num)) then maxRepl = toNumber(num) end end if (self:IsMappingActive(info) and (currentOrder < maxRepl)) then return Grichelde.ICONS.MOVE_DOWN else return Grichelde.ICONS.MOVE_DOWN_DISABLED end end function Grichelde:GetDeleteImage(info) self:TracePrint("GetDeleteImage : info") for i = 0, #info do self:TracePrint("%d = %s", i, info[i]) end if (self:IsMappingActive(info)) then return Grichelde.ICONS.DELETE else return Grichelde.ICONS.DELETE_DISABLED end end function Grichelde:DeleteMapping(info) self:TracePrint("DeleteMapping : info") for i = 0, #info do self:TracePrint("%d = %s", i, info[i]) end local currentName = info[2] self:DebugPrint("delete option: %s", currentName) self.db.profile.replacements[currentName] = nil self:RefreshProfiles("DeleteMapping " .. currentName) local _, replNumber = self:SplitOnFirstMatch(currentName, "_") local newMapping = "replacement_" .. toNumber(replNumber - 1) self.dialog:SelectGroup(self.name, "replacements", newMapping) end function Grichelde:DeleteAllMappings() self:DebugPrint("DeleteAllMappings") -- do NOT set self.db.profile.replacements = {} it will break defaults tWipe(self.db.profile.replacements) self:AddEmptyMapping() self:RefreshProfiles("DeleteAllMappings") end