/trunk/Who Framed Watcher Wabbit/libs/AceConfig-3.0
-- as well as associate it with a slash command. |
-- @class file |
-- @name AceConfig-3.0 |
-- @release $Id: AceConfig-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $ |
-- @release $Id: AceConfig-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $ |
--[[ |
AceConfig-3.0 |
function AceConfig:RegisterOptionsTable(appName, options, slashcmd) |
local ok,msg = pcall(cfgreg.RegisterOptionsTable, self, appName, options) |
if not ok then error(msg, 2) end |
if slashcmd then |
if type(slashcmd) == "table" then |
for _,cmd in pairs(slashcmd) do |
--- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables. |
-- @class file |
-- @name AceConfigDialog-3.0 |
-- @release $Id: AceConfigDialog-3.0.lua 1169 2018-02-27 16:18:28Z nevcairiel $ |
-- @release $Id: AceConfigDialog-3.0.lua 1232 2020-04-14 22:21:22Z nevcairiel $ |
local LibStub = LibStub |
local gui = LibStub("AceGUI-3.0") |
local reg = LibStub("AceConfigRegistry-3.0") |
local MAJOR, MINOR = "AceConfigDialog-3.0", 66 |
local MAJOR, MINOR = "AceConfigDialog-3.0", 79 |
local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceConfigDialog then return end |
AceConfigDialog.OpenFrames = AceConfigDialog.OpenFrames or {} |
AceConfigDialog.Status = AceConfigDialog.Status or {} |
AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame") |
AceConfigDialog.tooltip = AceConfigDialog.tooltip or CreateFrame("GameTooltip", "AceConfigDialogTooltip", UIParent, "GameTooltipTemplate") |
AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {} |
AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {} |
AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {} |
-- Lua APIs |
local tconcat, tinsert, tsort, tremove, tsort = table.concat, table.insert, table.sort, table.remove, table.sort |
local tinsert, tsort, tremove = table.insert, table.sort, table.remove |
local strmatch, format = string.match, string.format |
local assert, loadstring, error = assert, loadstring, error |
local error = error |
local pairs, next, select, type, unpack, wipe, ipairs = pairs, next, select, type, unpack, wipe, ipairs |
local rawset, tostring, tonumber = rawset, tostring, tonumber |
local tostring, tonumber = tostring, tonumber |
local math_min, math_max, math_floor = math.min, math.max, math.floor |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: NORMAL_FONT_COLOR, GameTooltip, StaticPopupDialogs, ACCEPT, CANCEL, StaticPopup_Show |
-- GLOBALS: NORMAL_FONT_COLOR, ACCEPT, CANCEL |
-- GLOBALS: PlaySound, GameFontHighlight, GameFontHighlightSmall, GameFontHighlightLarge |
-- GLOBALS: CloseSpecialWindows, InterfaceOptions_AddCategory, geterrorhandler |
return geterrorhandler()(err) |
end |
local function CreateDispatcher(argCount) |
local code = [[ |
local xpcall, eh = ... |
local method, ARGS |
local function call() return method(ARGS) end |
local function dispatch(func, ...) |
method = func |
if not method then return end |
ARGS = ... |
return xpcall(call, eh) |
end |
return dispatch |
]] |
local ARGS = {} |
for i = 1, argCount do ARGS[i] = "arg"..i end |
code = code:gsub("ARGS", tconcat(ARGS, ", ")) |
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) |
end |
local Dispatchers = setmetatable({}, {__index=function(self, argCount) |
local dispatcher = CreateDispatcher(argCount) |
rawset(self, argCount, dispatcher) |
return dispatcher |
end}) |
Dispatchers[0] = function(func) |
return xpcall(func, errorhandler) |
end |
local function safecall(func, ...) |
return Dispatchers[select("#", ...)](func, ...) |
if func then |
return xpcall(func, errorhandler, ...) |
end |
end |
local width_multiplier = 170 |
--[[ |
Group Types |
Tree - All Descendant Groups will all become nodes on the tree, direct child options will appear above the tree |
- Descendant Groups with inline=true and thier children will not become nodes |
- Descendant Groups with inline=true and thier children will not become nodes |
Tab - Direct Child Groups will become tabs, direct child options will appear above the tab control |
- Grandchild groups will default to inline unless specified otherwise |
- Grandchild groups will default to inline unless specified otherwise |
Select- Same as Tab but with entries in a dropdown rather than tabs |
Inline Groups |
- Will not become nodes of a select group, they will be effectivly part of thier parent group seperated by a border |
- If declared on a direct child of a root node of a select group, they will appear above the group container control |
- When a group is displayed inline, all descendants will also be inline members of the group |
- Will not become nodes of a select group, they will be effectivly part of thier parent group seperated by a border |
- If declared on a direct child of a root node of a select group, they will appear above the group container control |
- When a group is displayed inline, all descendants will also be inline members of the group |
]] |
local function GetOptionsMemberValue(membername, option, options, path, appName, ...) |
--get definition for the member |
local inherits = isInherited[membername] |
--get the member of the option, traversing the tree if it can be inherited |
local member |
if inherits then |
local group = options |
if group[membername] ~= nil then |
else |
member = option[membername] |
end |
--check if we need to call a functon, or if we have a literal value |
if ( not allIsLiteral[membername] ) and ( type(member) == "function" or ((not stringIsLiteral[membername]) and type(member) == "string") ) then |
--We have a function to call |
local handler |
local group = options |
handler = group.handler or handler |
for i = 1, #path do |
group = GetSubOption(group, path[i]) |
info[i] = path[i] |
handler = group.handler or handler |
end |
info.options = options |
info.appName = appName |
info[0] = appName |
info.type = option.type |
info.uiType = "dialog" |
info.uiName = MAJOR |
local a, b, c ,d |
local a, b, c ,d |
--using 4 returns for the get of a color type, increase if a type needs more |
if type(member) == "function" then |
--Call the function |
return a,b,c,d |
else |
--The value isnt a function to call, return it |
return member |
end |
return member |
end |
end |
--[[calls an options function that could be inherited, method name or function ref |
return NameA:upper() < NameB:upper() |
end |
if OrderA < 0 then |
if OrderB > 0 then |
if OrderB >= 0 then |
return false |
end |
else |
local function BuildSortedOptionsTable(group, keySort, opts, options, path, appName) |
tempOrders = new() |
tempNames = new() |
if group.plugins then |
for plugin, t in pairs(group.plugins) do |
for k, v in pairs(t) do |
end |
end |
end |
for k, v in pairs(group.args) do |
if not opts[k] then |
tinsert(keySort, k) |
end |
local function CleanUserData(widget, event) |
local user = widget:GetUserDataTable() |
if user.path then |
-- - Gets a status table for the given appname and options path. |
-- @param appName The application name as given to `:RegisterOptionsTable()` |
-- @param path The path to the options (a table with all group keys) |
-- @return |
-- @return |
function AceConfigDialog:GetStatusTable(appName, path) |
local status = self.Status |
function AceConfigDialog:SelectGroup(appName, ...) |
local path = new() |
local app = reg:GetOptionsTable(appName) |
if not app then |
error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2) |
status.groups = {} |
end |
status = status.groups |
local treevalue |
local treestatus |
local treevalue |
local treestatus |
for n = 1, select("#",...) do |
local key = select(n, ...) |
--the selected group will be overwritten if a child is the final target but still needs to be open |
treestatus.selected = treevalue |
treestatus.groups[treevalue] = true |
end |
--move to the next group in the path |
group = GetSubOption(group, key) |
if not group then |
if not group then |
break |
end |
tinsert(path, key) |
end |
status = status.groups |
end |
del(path) |
reg:NotifyChange(appName) |
end |
end |
local function OptionOnMouseOver(widget, event) |
--show a tooltip/set the status bar to the desc text |
local options = user.options |
local path = user.path |
local appName = user.appName |
local tooltip = AceConfigDialog.tooltip |
GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT") |
tooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT") |
local name = GetOptionsMemberValue("name", opt, options, path, appName) |
local desc = GetOptionsMemberValue("desc", opt, options, path, appName) |
local usage = GetOptionsMemberValue("usage", opt, options, path, appName) |
local descStyle = opt.descStyle |
if descStyle and descStyle ~= "tooltip" then return end |
GameTooltip:SetText(name, 1, .82, 0, true) |
tooltip:SetText(name, 1, .82, 0, true) |
if opt.type == "multiselect" then |
GameTooltip:AddLine(user.text, 0.5, 0.5, 0.8, true) |
end |
tooltip:AddLine(user.text, 0.5, 0.5, 0.8, true) |
end |
if type(desc) == "string" then |
GameTooltip:AddLine(desc, 1, 1, 1, true) |
tooltip:AddLine(desc, 1, 1, 1, true) |
end |
if type(usage) == "string" then |
GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true) |
tooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true) |
end |
GameTooltip:Show() |
tooltip:Show() |
end |
local function OptionOnMouseLeave(widget, event) |
GameTooltip:Hide() |
AceConfigDialog.tooltip:Hide() |
end |
local function GetFuncName(option) |
return "set" |
end |
end |
do |
local frame = AceConfigDialog.popup |
if not frame then |
frame = CreateFrame("Frame", nil, UIParent) |
AceConfigDialog.popup = frame |
frame:Hide() |
frame:SetPoint("CENTER", UIParent, "CENTER") |
frame:SetSize(320, 72) |
frame:SetFrameStrata("TOOLTIP") |
frame:SetScript("OnKeyDown", function(self, key) |
if key == "ESCAPE" then |
self:SetPropagateKeyboardInput(false) |
if self.cancel:IsShown() then |
self.cancel:Click() |
else -- Showing a validation error |
self:Hide() |
end |
else |
self:SetPropagateKeyboardInput(true) |
end |
end) |
if WOW_PROJECT_ID == WOW_PROJECT_CLASSIC then |
frame:SetBackdrop({ |
bgFile = [[Interface\DialogFrame\UI-DialogBox-Background-Dark]], |
edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]], |
tile = true, |
tileSize = 32, |
edgeSize = 32, |
insets = { left = 11, right = 11, top = 11, bottom = 11 }, |
}) |
else |
local border = CreateFrame("Frame", nil, frame, "DialogBorderDarkTemplate") |
border:SetAllPoints(frame) |
end |
local text = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight") |
text:SetSize(290, 0) |
text:SetPoint("TOP", 0, -16) |
frame.text = text |
local function newButton(text) |
local button = CreateFrame("Button", nil, frame) |
button:SetSize(128, 21) |
button:SetNormalFontObject(GameFontNormal) |
button:SetHighlightFontObject(GameFontHighlight) |
button:SetNormalTexture(130763) -- "Interface\\Buttons\\UI-DialogBox-Button-Up" |
button:GetNormalTexture():SetTexCoord(0.0, 1.0, 0.0, 0.71875) |
button:SetPushedTexture(130761) -- "Interface\\Buttons\\UI-DialogBox-Button-Down" |
button:GetPushedTexture():SetTexCoord(0.0, 1.0, 0.0, 0.71875) |
button:SetHighlightTexture(130762) -- "Interface\\Buttons\\UI-DialogBox-Button-Highlight" |
button:GetHighlightTexture():SetTexCoord(0.0, 1.0, 0.0, 0.71875) |
button:SetText(text) |
return button |
end |
local accept = newButton(ACCEPT) |
accept:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -6, 16) |
frame.accept = accept |
local cancel = newButton(CANCEL) |
cancel:SetPoint("LEFT", accept, "RIGHT", 13, 0) |
frame.cancel = cancel |
end |
end |
local function confirmPopup(appName, rootframe, basepath, info, message, func, ...) |
if not StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] then |
StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] = {} |
end |
local t = StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] |
for k in pairs(t) do |
t[k] = nil |
end |
t.text = message |
t.button1 = ACCEPT |
t.button2 = CANCEL |
t.preferredIndex = STATICPOPUP_NUMDIALOGS |
local dialog, oldstrata |
t.OnAccept = function() |
safecall(func, unpack(t)) |
if dialog and oldstrata then |
dialog:SetFrameStrata(oldstrata) |
end |
local frame = AceConfigDialog.popup |
frame:Show() |
frame.text:SetText(message) |
-- From StaticPopup.lua |
-- local height = 32 + text:GetHeight() + 2; |
-- height = height + 6 + accept:GetHeight() |
-- We add 32 + 2 + 6 + 21 (button height) == 61 |
local height = 61 + frame.text:GetHeight() |
frame:SetHeight(height) |
frame.accept:ClearAllPoints() |
frame.accept:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -6, 16) |
frame.cancel:Show() |
local t = {...} |
local tCount = select("#", ...) |
frame.accept:SetScript("OnClick", function(self) |
safecall(func, unpack(t, 1, tCount)) -- Manually set count as unpack() stops on nil (bug with #table) |
AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl)) |
frame:Hide() |
self:SetScript("OnClick", nil) |
frame.cancel:SetScript("OnClick", nil) |
del(info) |
end |
t.OnCancel = function() |
if dialog and oldstrata then |
dialog:SetFrameStrata(oldstrata) |
end |
end) |
frame.cancel:SetScript("OnClick", function(self) |
AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl)) |
frame:Hide() |
self:SetScript("OnClick", nil) |
frame.accept:SetScript("OnClick", nil) |
del(info) |
end |
for i = 1, select("#", ...) do |
t[i] = select(i, ...) or false |
end |
t.timeout = 0 |
t.whileDead = 1 |
t.hideOnEscape = 1 |
dialog = StaticPopup_Show("ACECONFIGDIALOG30_CONFIRM_DIALOG") |
if dialog then |
oldstrata = dialog:GetFrameStrata() |
dialog:SetFrameStrata("TOOLTIP") |
end |
end) |
end |
local function validationErrorPopup(message) |
if not StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] then |
StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] = {} |
end |
local t = StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] |
t.text = message |
t.button1 = OKAY |
t.preferredIndex = STATICPOPUP_NUMDIALOGS |
local dialog, oldstrata |
t.OnAccept = function() |
if dialog and oldstrata then |
dialog:SetFrameStrata(oldstrata) |
end |
end |
t.timeout = 0 |
t.whileDead = 1 |
t.hideOnEscape = 1 |
local frame = AceConfigDialog.popup |
frame:Show() |
frame.text:SetText(message) |
-- From StaticPopup.lua |
-- local height = 32 + text:GetHeight() + 2; |
-- height = height + 6 + accept:GetHeight() |
-- We add 32 + 2 + 6 + 21 (button height) == 61 |
local height = 61 + frame.text:GetHeight() |
frame:SetHeight(height) |
dialog = StaticPopup_Show("ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG") |
if dialog then |
oldstrata = dialog:GetFrameStrata() |
dialog:SetFrameStrata("TOOLTIP") |
end |
frame.accept:ClearAllPoints() |
frame.accept:SetPoint("BOTTOM", frame, "BOTTOM", 0, 16) |
frame.cancel:Hide() |
frame.accept:SetScript("OnClick", function() |
frame:Hide() |
end) |
end |
local function ActivateControl(widget, event, ...) |
end |
end |
end |
local success |
if validated and option.type ~= "execute" then |
if type(validate) == "string" then |
if not success then validated = false end |
end |
end |
local rootframe = user.rootframe |
if not validated or type(validated) == "string" then |
if not validated then |
del(info) |
return true |
else |
local confirmText = option.confirmText |
--call confirm func/method |
if type(confirm) == "string" then |
confirmText = confirmText.." - "..desc |
end |
end |
local iscustom = user.rootframe:GetUserData("iscustom") |
local rootframe |
if iscustom then |
rootframe = user.rootframe |
end |
--full refresh of the frame, some controls dont cause this on all events |
if option.type == "color" then |
if event == "OnValueConfirmed" then |
if iscustom then |
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath)) |
else |
local function MultiControlOnClosed(widget, event, ...) |
local user = widget:GetUserDataTable() |
if user.valuechanged then |
if user.valuechanged and not widget:IsReleasing() then |
local iscustom = user.rootframe:GetUserData("iscustom") |
local basepath = user.rootframe:GetUserData("basepath") or emptyTbl |
if iscustom then |
control:SetCallback("OnEnter", OptionOnMouseOver) |
end |
local function CreateControl(userControlType, fallbackControlType) |
local control |
if userControlType then |
control = gui:Create(userControlType) |
if not control then |
geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(userControlType))) |
end |
end |
if not control then |
control = gui:Create(fallbackControlType) |
end |
return control |
end |
local function sortTblAsStrings(x,y) |
return tostring(x) < tostring(y) -- Support numbers as keys |
end |
--[[ |
options - root of the options table being fed |
container - widget that controls will be placed in |
else |
GroupContainer = gui:Create("SimpleGroup") |
end |
GroupContainer.width = "fill" |
GroupContainer:SetLayout("flow") |
container:AddChild(GroupContainer) |
else |
--Control to feed |
local control |
local name = GetOptionsMemberValue("name", v, options, path, appName) |
if v.type == "execute" then |
local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName) |
local image, width, height = GetOptionsMemberValue("image",v, options, path, appName) |
if type(image) == "string" or type(image) == "number" then |
control = gui:Create("Icon") |
local iconControl = type(image) == "string" or type(image) == "number" |
control = CreateControl(v.dialogControl or v.control, iconControl and "Icon" or "Button") |
if iconControl then |
if not width then |
width = GetOptionsMemberValue("imageWidth",v, options, path, appName) |
end |
control:SetImageSize(width, height) |
control:SetLabel(name) |
else |
control = gui:Create("Button") |
control:SetText(name) |
end |
control:SetCallback("OnClick",ActivateControl) |
elseif v.type == "input" then |
local controlType = v.dialogControl or v.control or (v.multiline and "MultiLineEditBox") or "EditBox" |
control = gui:Create(controlType) |
if not control then |
geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType))) |
control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox") |
end |
control = CreateControl(v.dialogControl or v.control, v.multiline and "MultiLineEditBox" or "EditBox") |
if v.multiline and control.SetNumLines then |
control:SetNumLines(tonumber(v.multiline) or 4) |
end |
control:SetText(text) |
elseif v.type == "toggle" then |
control = gui:Create("CheckBox") |
control = CreateControl(v.dialogControl or v.control, "CheckBox") |
control:SetLabel(name) |
control:SetTriState(v.tristate) |
local value = GetOptionsMemberValue("get",v, options, path, appName) |
control:SetValue(value) |
control:SetCallback("OnValueChanged",ActivateControl) |
if v.descStyle == "inline" then |
local desc = GetOptionsMemberValue("desc", v, options, path, appName) |
control:SetDescription(desc) |
end |
local image = GetOptionsMemberValue("image", v, options, path, appName) |
local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName) |
if type(image) == "string" or type(image) == "number" then |
if type(imageCoords) == "table" then |
control:SetImage(image, unpack(imageCoords)) |
end |
end |
elseif v.type == "range" then |
control = gui:Create("Slider") |
control = CreateControl(v.dialogControl or v.control, "Slider") |
control:SetLabel(name) |
control:SetSliderValues(v.softMin or v.min or 0, v.softMax or v.max or 100, v.bigStep or v.step or 0) |
control:SetIsPercent(v.isPercent) |
elseif v.type == "select" then |
local values = GetOptionsMemberValue("values", v, options, path, appName) |
local sorting = GetOptionsMemberValue("sorting", v, options, path, appName) |
if v.style == "radio" then |
local disabled = CheckOptionDisabled(v, options, path, appName) |
local width = GetOptionsMemberValue("width",v,options,path,appName) |
control:PauseLayout() |
local optionValue = GetOptionsMemberValue("get",v, options, path, appName) |
local t = {} |
for value, text in pairs(values) do |
t[#t+1]=value |
if not sorting then |
sorting = {} |
for value, text in pairs(values) do |
sorting[#sorting+1]=value |
end |
tsort(sorting, sortTblAsStrings) |
end |
tsort(t) |
for k, value in ipairs(t) do |
for k, value in ipairs(sorting) do |
local text = values[value] |
local radio = gui:Create("CheckBox") |
radio:SetLabel(text) |
control:ResumeLayout() |
control:DoLayout() |
else |
local controlType = v.dialogControl or v.control or "Dropdown" |
control = gui:Create(controlType) |
if not control then |
geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType))) |
control = gui:Create("Dropdown") |
end |
control = CreateControl(v.dialogControl or v.control, "Dropdown") |
local itemType = v.itemControl |
if itemType and not gui:GetWidgetVersion(itemType) then |
geterrorhandler()(("Invalid Custom Item Type - %s"):format(tostring(itemType))) |
itemType = nil |
end |
control:SetLabel(name) |
control:SetList(values, nil, itemType) |
control:SetList(values, sorting, itemType) |
local value = GetOptionsMemberValue("get",v, options, path, appName) |
if not values[value] then |
value = nil |
elseif v.type == "multiselect" then |
local values = GetOptionsMemberValue("values", v, options, path, appName) |
local disabled = CheckOptionDisabled(v, options, path, appName) |
local controlType = v.dialogControl or v.control |
local valuesort = new() |
if values then |
for value, text in pairs(values) do |
end |
end |
tsort(valuesort) |
local controlType = v.dialogControl or v.control |
if controlType then |
control = gui:Create(controlType) |
if not control then |
control:ResumeLayout() |
control:DoLayout() |
end |
del(valuesort) |
elseif v.type == "color" then |
control = gui:Create("ColorPicker") |
control = CreateControl(v.dialogControl or v.control, "ColorPicker") |
control:SetLabel(name) |
control:SetHasAlpha(GetOptionsMemberValue("hasAlpha",v, options, path, appName)) |
control:SetColor(GetOptionsMemberValue("get",v, options, path, appName)) |
control:SetCallback("OnValueConfirmed",ActivateControl) |
elseif v.type == "keybinding" then |
control = gui:Create("Keybinding") |
control = CreateControl(v.dialogControl or v.control, "Keybinding") |
control:SetLabel(name) |
control:SetKey(GetOptionsMemberValue("get",v, options, path, appName)) |
control:SetCallback("OnKeyChanged",ActivateControl) |
elseif v.type == "header" then |
control = gui:Create("Heading") |
control = CreateControl(v.dialogControl or v.control, "Heading") |
control:SetText(name) |
control.width = "fill" |
elseif v.type == "description" then |
control = gui:Create("Label") |
control = CreateControl(v.dialogControl or v.control, "Label") |
control:SetText(name) |
local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName) |
if fontSize == "medium" then |
control:SetFontObject(GameFontHighlight) |
else -- small or invalid |
control:SetFontObject(GameFontHighlightSmall) |
end |
local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName) |
local image, width, height = GetOptionsMemberValue("image",v, options, path, appName) |
if type(image) == "string" or type(image) == "number" then |
if not width then |
width = GetOptionsMemberValue("imageWidth",v, options, path, appName) |
InjectInfo(control, options, v, path, rootframe, appName) |
container:AddChild(control) |
end |
end |
end |
tremove(path) |
local option = user.option |
local path = user.path |
local appName = user.appName |
local tooltip = AceConfigDialog.tooltip |
local feedpath = new() |
for i = 1, #path do |
feedpath[i] = path[i] |
local name = GetOptionsMemberValue("name", group, options, feedpath, appName) |
local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName) |
GameTooltip:SetOwner(button, "ANCHOR_NONE") |
tooltip:SetOwner(button, "ANCHOR_NONE") |
tooltip:ClearAllPoints() |
if widget.type == "TabGroup" then |
GameTooltip:SetPoint("BOTTOM",button,"TOP") |
tooltip:SetPoint("BOTTOM",button,"TOP") |
else |
GameTooltip:SetPoint("LEFT",button,"RIGHT") |
tooltip:SetPoint("LEFT",button,"RIGHT") |
end |
GameTooltip:SetText(name, 1, .82, 0, true) |
tooltip:SetText(name, 1, .82, 0, true) |
if type(desc) == "string" then |
GameTooltip:AddLine(desc, 1, 1, 1, true) |
tooltip:AddLine(desc, 1, 1, 1, true) |
end |
GameTooltip:Show() |
tooltip:Show() |
end |
local function TreeOnButtonLeave(widget, event, value, button) |
GameTooltip:Hide() |
AceConfigDialog.tooltip:Hide() |
end |
local function GroupExists(appName, options, path, uniquevalue) |
if not uniquevalue then return false end |
local feedpath = new() |
local temppath = new() |
for i = 1, #path do |
feedpath[i] = path[i] |
end |
BuildPath(feedpath, ("\001"):split(uniquevalue)) |
local group = options |
for i = 1, #feedpath do |
local v = feedpath[i] |
temppath[i] = v |
group = GetSubOption(group, v) |
if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then |
if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then |
del(feedpath) |
del(temppath) |
return false |
return false |
end |
end |
del(feedpath) |
end |
BuildPath(feedpath, ("\001"):split(uniquevalue)) |
local group = options |
for i = 1, #feedpath do |
group = GetSubOption(group, feedpath[i]) |
end |
widget:ReleaseChildren() |
AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath) |
tab:SetCallback("OnGroupSelected", GroupSelected) |
tab:SetCallback("OnTabEnter", TreeOnButtonEnter) |
tab:SetCallback("OnTabLeave", TreeOnButtonLeave) |
local status = AceConfigDialog:GetStatusTable(appName, path) |
if not status.groups then |
status.groups = {} |
break |
end |
end |
container:AddChild(tab) |
elseif grouptype == "select" then |
if firstgroup then |
select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup) |
end |
select.width = "fill" |
select.height = "fill" |
local tree = gui:Create("TreeGroup") |
InjectInfo(tree, options, group, path, rootframe, appName) |
tree:EnableButtonTooltips(false) |
tree.width = "fill" |
tree.height = "fill" |
tree:SetCallback("OnGroupSelected", GroupSelected) |
tree:SetCallback("OnButtonEnter", TreeOnButtonEnter) |
tree:SetCallback("OnButtonLeave", TreeOnButtonLeave) |
local status = AceConfigDialog:GetStatusTable(appName, path) |
if not status.groups then |
status.groups = {} |
end |
this.closing[appName] = nil |
end |
if this.closeAll then |
for k, v in pairs(AceConfigDialog.OpenFrames) do |
if not this.closeAllOverride[k] then |
this.closeAll = nil |
wipe(this.closeAllOverride) |
end |
for appName in pairs(this.apps) do |
if AceConfigDialog.OpenFrames[appName] then |
local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable() |
local options = app("dialog", MAJOR) |
local f |
local path = new() |
local name = GetOptionsMemberValue("name", options, options, path, appName) |
--If an optional path is specified add it to the path table before feeding the options |
--as container is optional as well it may contain the first element of the path |
if type(container) == "string" then |
for n = 1, select("#",...) do |
tinsert(path, (select(n, ...))) |
end |
local option = options |
if type(container) == "table" and container.type == "BlizOptionsGroup" and #path > 0 then |
for i = 1, #path do |
end |
name = format("%s - %s", name, GetOptionsMemberValue("name", option, options, path, appName)) |
end |
--if a container is given feed into that |
if container then |
f = container |
-- @param name A descriptive name to display in the options tree (defaults to appName) |
-- @param parent The parent to use in the interface options tree. |
-- @param ... The path in the options table to feed into the interface options panel. |
-- @return The reference to the frame registered into the Interface Options. |
-- @return The reference to the frame registered into the Interface Options. |
function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...) |
local BlizOptions = AceConfigDialog.BlizOptions |
local key = appName |
for n = 1, select("#", ...) do |
key = key.."\001"..select(n, ...) |
end |
if not BlizOptions[appName] then |
BlizOptions[appName] = {} |
end |
if not BlizOptions[appName][key] then |
local group = gui:Create("BlizOptionsGroup") |
BlizOptions[appName][key] = group |
--- AceConfigCmd-3.0 handles access to an options table through the "command line" interface via the ChatFrames. |
-- @class file |
-- @name AceConfigCmd-3.0 |
-- @release $Id: AceConfigCmd-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $ |
-- @release $Id: AceConfigCmd-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $ |
--[[ |
AceConfigCmd-3.0 |
-- pickfirstset() - picks the first non-nil value and returns it |
local function pickfirstset(...) |
local function pickfirstset(...) |
for i=1,select("#",...) do |
if select(i,...)~=nil then |
return select(i,...) |
info.arg = tab.arg |
info.option = tab |
info.type = tab.type |
if type(method)=="function" then |
return method(info, ...) |
else |
-- do_final() - do the final step (set/execute) along with validation and confirmation |
local function do_final(info, inputpos, tab, methodtype, ...) |
if info.validate then |
if info.validate then |
local res = callmethod(info,inputpos,tab,"validate",...) |
if type(res)=="string" then |
usererr(info, inputpos, "'"..strsub(info.input, inputpos).."' - "..res) |
end |
end |
-- console ignores .confirm |
callmethod(info,inputpos,tab,methodtype, ...) |
end |
if val~=nil then |
if val==false then |
val=nil |
elseif not types[type(val)] then |
err(info, inputpos, "'" .. paramname.. "' - "..errormsg) |
elseif not types[type(val)] then |
err(info, inputpos, "'" .. paramname.. "' - "..errormsg) |
end |
info[paramname] = val |
info[paramname.."_at"] = depth |
local dummytable={} |
local function iterateargs(tab) |
if not tab.plugins then |
return pairs(tab.args) |
if not tab.plugins then |
return pairs(tab.args) |
end |
local argtabkey,argtab=next(tab.plugins) |
local v |
return function(_, k) |
while argtab do |
k,v = next(argtab, k) |
if not noHead then |
print("|cff33ff99"..info.appName.."|r: Arguments to |cffffff78/"..info[0].."|r "..strsub(info.input,1,inputpos-1)..":") |
end |
local sortTbl = {} -- [1..n]=name |
local refTbl = {} -- [name]=tableref |
for k,v in iterateargs(tab) do |
if not refTbl[k] then -- a plugin overriding something in .args |
tinsert(sortTbl, k) |
refTbl[k] = v |
end |
end |
tsort(sortTbl, function(one, two) |
tsort(sortTbl, function(one, two) |
local o1 = refTbl[one].order or 100 |
local o2 = refTbl[two].order or 100 |
if type(o1) == "function" or type(o1) == "string" then |
if o1==o2 then return tostring(one)<tostring(two) end -- compare names |
return o1<o2 |
end) |
for i = 1, #sortTbl do |
local k = sortTbl[i] |
local v = refTbl[k] |
return s |
end |
-- handle() - selfrecursing function that processes input->optiontable |
-- handle() - selfrecursing function that processes input->optiontable |
-- - depth - starts at 0 |
-- - retfalse - return false rather than produce error if a match is not found (used by inlined groups) |
local oldfunc,oldfunc_at = getparam(info,inputpos,tab,depth,"func",functypes,funcmsg) |
local oldvalidate,oldvalidate_at = getparam(info,inputpos,tab,depth,"validate",functypes,funcmsg) |
--local oldconfirm,oldconfirm_at = getparam(info,inputpos,tab,depth,"confirm",functypes,funcmsg) |
------------------------------------------------------------------- |
-- Act according to .type of this table |
if tab.type=="group" then |
------------ group -------------------------------------------- |
if type(tab.args)~="table" then err(info, inputpos) end |
if tab.plugins and type(tab.plugins)~="table" then err(info,inputpos) end |
-- grab next arg from input |
local _,nextpos,arg = (info.input):find(" *([^ ]+) *", inputpos) |
if not arg then |
return |
end |
nextpos=nextpos+1 |
-- loop .args and try to find a key with a matching name |
for k,v in iterateargs(tab) do |
if not(type(k)=="string" and type(v)=="table" and type(v.type)=="string") then err(info,inputpos, "options table child '"..tostring(k).."' is malformed") end |
-- is this child an inline group? if so, traverse into it |
if v.type=="group" and pickfirstset(v.cmdInline, v.inline, false) then |
info[depth+1] = k |
return handle(info,nextpos,v,depth+1) |
end |
end |
-- no match |
-- no match |
if retfalse then |
-- restore old infotable members and return false to indicate failure |
info.handler,info.handler_at = oldhandler,oldhandler_at |
--info.confirm,info.confirm_at = oldconfirm,oldconfirm_at |
return false |
end |
-- couldn't find the command, display error |
usererr(info, inputpos, "'"..arg.."' - " .. L["unknown argument"]) |
return |
end |
local str = strsub(info.input,inputpos); |
if tab.type=="execute" then |
------------ execute -------------------------------------------- |
do_final(info, inputpos, tab, "func") |
elseif tab.type=="input" then |
------------ input -------------------------------------------- |
local res = true |
if tab.pattern then |
if not(type(tab.pattern)=="string") then err(info, inputpos, "'pattern' - expected a string") end |
return |
end |
end |
do_final(info, inputpos, tab, "set", str) |
elseif tab.type=="toggle" then |
------------ toggle -------------------------------------------- |
local b |
else |
b = not b |
end |
elseif str==L["on"] then |
b = true |
elseif str==L["off"] then |
end |
return |
end |
do_final(info, inputpos, tab, "set", b) |
elseif tab.type=="range" then |
------------ range -------------------------------------------- |
local val = tonumber(str) |
usererr(info, inputpos, val.." - "..format(L["must be equal to or lower than %s"], tostring(info.max)) ) |
return |
end |
do_final(info, inputpos, tab, "set", val) |
elseif tab.type=="select" then |
------------ select ------------------------------------ |
local str = strtrim(strlower(str)) |
local values = tab.values |
if type(values) == "function" or type(values) == "string" then |
info.values = values |
values = callmethod(info, inputpos, tab, "values") |
info.values = nil |
end |
if str == "" then |
local b = callmethod(info, inputpos, tab, "get") |
local fmt = "|cffffff78- [%s]|r %s" |
end |
local ok |
for k,v in pairs(values) do |
for k,v in pairs(values) do |
if strlower(k)==str then |
str = k -- overwrite with key (in case of case mismatches) |
ok = true |
usererr(info, inputpos, "'"..str.."' - "..L["unknown selection"]) |
return |
end |
do_final(info, inputpos, tab, "set", str) |
elseif tab.type=="multiselect" then |
------------ multiselect ------------------------------------------- |
local str = strtrim(strlower(str)) |
local values = tab.values |
if type(values) == "function" or type(values) == "string" then |
info.values = values |
values = callmethod(info, inputpos, tab, "values") |
info.values = nil |
end |
if str == "" then |
local fmt = "|cffffff78- [%s]|r %s" |
local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r" |
end |
return |
end |
--build a table of the selections, checking that they exist |
--parse for =on =off =default in the process |
--table will be key = true for options that should toggle, key = [on|off|default] for options to be set |
--parse option=on etc |
local opt, val = v:match('(.+)=(.+)') |
--get option if toggling |
if not opt then |
opt = v |
if not opt then |
opt = v |
end |
--check that the opt is valid |
local ok |
for k,v in pairs(values) do |
for k,v in pairs(values) do |
if strlower(k)==opt then |
opt = k -- overwrite with key (in case of case mismatches) |
ok = true |
break |
end |
end |
if not ok then |
usererr(info, inputpos, "'"..opt.."' - "..L["unknown selection"]) |
return |
end |
--check that if val was supplied it is valid |
if val then |
if val == L["on"] or val == L["off"] or (tab.tristate and val == L["default"]) then |
sels[opt] = true |
end |
end |
for opt, val in pairs(sels) do |
local newval |
if (val == true) then |
--toggle the option |
local b = callmethod(info, inputpos, tab, "get", opt) |
if tab.tristate then |
--cycle in true, nil, false order |
if b then |
newval = nil |
end |
end |
do_final(info, inputpos, tab, "set", opt, newval) |
end |
elseif tab.type=="color" then |
------------ color -------------------------------------------- |
local str = strtrim(strlower(str)) |
--TODO: Show current value |
return |
end |
local r, g, b, a |
local hasAlpha = tab.hasAlpha |
if type(hasAlpha) == "function" or type(hasAlpha) == "string" then |
info.hasAlpha = hasAlpha |
hasAlpha = callmethod(info, inputpos, tab, 'hasAlpha') |
info.hasAlpha = nil |
end |
if hasAlpha then |
if str:len() == 8 and str:find("^%x*$") then |
--parse a hex string |
usererr(info, inputpos, format(L["'%s' - expected 'RRGGBBAA' or 'r g b a'."], str)) |
return |
end |
if r >= 0.0 and r <= 1.0 and g >= 0.0 and g <= 1.0 and b >= 0.0 and b <= 1.0 and a >= 0.0 and a <= 1.0 then |
--values are valid |
elseif r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 and a >= 0 and a <= 255 then |
usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0-1 or 0-255."], str)) |
end |
end |
do_final(info, inputpos, tab, "set", r,g,b,a) |
elseif tab.type=="keybinding" then |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceConsole-3.0") |
-- -- Use AceConsole-3.0 to register a Chat Command |
-- MyAddon:RegisterChatCommand("mychat", "ChatCommand") |
-- |
-- |
-- -- Show the GUI if no input is supplied, otherwise handle the chat input. |
-- function MyAddon:ChatCommand(input) |
-- -- Assuming "MyOptions" is the appName of a valid options table |
error([[Usage: HandleCommand("slashcmd", "appName", "input"): 'appName' - no options table "]]..tostring(appName)..[[" has been registered]], 2) |
end |
local options = assert( optgetter("cmd", MAJOR) ) |
local info = { -- Don't try to recycle this, it gets handed off to callbacks and whatnot |
[0] = slashcmd, |
appName = appName, |
uiType = "cmd", |
uiName = MAJOR, |
} |
handle(info, 1, options, 0) -- (info, inputpos, table, depth) |
end |
-- * Valid **uiTypes**: "cmd", "dropdown", "dialog". This is verified by the library at call time. \\ |
-- * The **uiName** field is expected to contain the full name of the calling addon, including version, e.g. "FooBar-1.0". This is verified by the library at call time.\\ |
-- * The **appName** field is the options table name as given at registration time \\ |
-- |
-- |
-- :IterateOptionsTables() (and :GetOptionsTable() if only given one argument) return a function reference that the requesting config handling addon must call with valid "uiType", "uiName". |
-- @class file |
-- @name AceConfigRegistry-3.0 |
-- @release $Id: AceConfigRegistry-3.0.lua 1169 2018-02-27 16:18:28Z nevcairiel $ |
-- @release $Id: AceConfigRegistry-3.0.lua 1207 2019-06-23 12:08:33Z nevcairiel $ |
local CallbackHandler = LibStub("CallbackHandler-1.0") |
local MAJOR, MINOR = "AceConfigRegistry-3.0", 18 |
local MAJOR, MINOR = "AceConfigRegistry-3.0", 20 |
local AceConfigRegistry = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceConfigRegistry then return end |
AceConfigRegistry.validated = { |
-- list of options table names ran through :ValidateOptionsTable automatically. |
-- list of options table names ran through :ValidateOptionsTable automatically. |
-- CLEARED ON PURPOSE, since newer versions may have newer validators |
cmd = {}, |
dropdown = {}, |
local optstringfunc={["nil"]=true,["string"]=true,["function"]=true, _="string or funcref"} |
local optstringnumberfunc={["nil"]=true,["string"]=true,["number"]=true,["function"]=true, _="string, number or funcref"} |
local optnumber={["nil"]=true,["number"]=true, _="number"} |
local optmethod={["nil"]=true,["string"]=true,["function"]=true, _="methodname or funcref"} |
local optmethodfalse={["nil"]=true,["string"]=true,["function"]=true,["boolean"]={[false]=true}, _="methodname, funcref or false"} |
local optmethodnumber={["nil"]=true,["string"]=true,["function"]=true,["number"]=true, _="methodname, funcref or number"} |
local optmethodtable={["nil"]=true,["string"]=true,["function"]=true,["table"]=true, _="methodname, funcref or table"} |
} |
local typedkeys={ |
header={}, |
header={ |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
description={ |
image=optstringnumberfunc, |
imageCoords=optmethodtable, |
imageHeight=optnumber, |
imageWidth=optnumber, |
fontSize=optstringfunc, |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
group={ |
args=istable, |
imageCoords=optmethodtable, |
imageHeight=optnumber, |
imageWidth=optnumber, |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
input={ |
pattern=optstring, |
tristate=optbool, |
image=optstringnumberfunc, |
imageCoords=optmethodtable, |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
tristate={ |
}, |
step=optnumber, |
bigStep=optnumber, |
isPercent=optbool, |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
select={ |
values=ismethodtable, |
sorting=optmethodtable, |
style={ |
["nil"]=true, |
["string"]={dropdown=true,radio=true}, |
["nil"]=true, |
["string"]={dropdown=true,radio=true}, |
_="string: 'dropdown' or 'radio'" |
}, |
control=optstring, |
}, |
color={ |
hasAlpha=optmethodbool, |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
keybinding={ |
-- TODO |
control=optstring, |
dialogControl=optstring, |
dropdownControl=optstring, |
}, |
} |
if type(options.type)~="string" then |
err(".type: expected a string, got a "..type(options.type), errlvl,...) |
end |
-- get type and 'typedkeys' member |
local tk = typedkeys[options.type] |
if not tk then |
err(".type: unknown type '"..options.type.."'", errlvl,...) |
end |
-- make sure that all options[] are known parameters |
for k,v in pairs(options) do |
if not (tk[k] or basekeys[k]) then |
AceConfigRegistry:ValidateOptionsTable(options, appName, errlvl) -- upgradable |
AceConfigRegistry.validated[uiType][appName] = true |
end |
return options |
return options |
end |
elseif type(options)=="function" then |
AceConfigRegistry.tables[appName] = function(uiType, uiName, errlvl) |
if not f then |
return nil |
end |
if uiType then |
return f(uiType,uiName,1) -- get the table for us |
else |