57a5cdabdf
Imported from /srv/add01/wow-ascension/Interface/AddOns/Bartender4 — the build Ascension's WotLK 3.3.5 client ships. Single vendored drop: Ascension's build process bundles their custom patches with the standard CurseForge packager output (embedded libs), and the individual patches aren't published separately. Net delta vs Nevcairiel 4.4.2, excluding bundled libs and CRLF normalization: 21 files, 2213+/52- — the Ascension-specific adaptations for WotLK 3.3.5 hero classes / custom action systems. License: All rights reserved (per .toc).
1893 lines
55 KiB
Lua
1893 lines
55 KiB
Lua
--- 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 939 2010-06-19 07:26:17Z nevcairiel $
|
|
|
|
local LibStub = LibStub
|
|
local MAJOR, MINOR = "AceConfigDialog-3.0", 48
|
|
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.frame.apps = AceConfigDialog.frame.apps or {}
|
|
AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
|
|
|
|
local gui = LibStub("AceGUI-3.0")
|
|
local reg = LibStub("AceConfigRegistry-3.0")
|
|
|
|
-- Lua APIs
|
|
local tconcat, tinsert, tsort, tremove = table.concat, table.insert, table.sort, table.remove
|
|
local strmatch, format = string.match, string.format
|
|
local assert, loadstring, error = assert, loadstring, error
|
|
local pairs, next, select, type, unpack = pairs, next, select, type, unpack
|
|
local rawset, tostring, tonumber = rawset, 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: PlaySound, GameFontHighlight, GameFontHighlightSmall, GameFontHighlightLarge
|
|
-- GLOBALS: CloseSpecialWindows, InterfaceOptions_AddCategory, geterrorhandler
|
|
|
|
local emptyTbl = {}
|
|
|
|
--[[
|
|
xpcall safecall implementation
|
|
]]
|
|
local xpcall = xpcall
|
|
|
|
local function errorhandler(err)
|
|
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, ...)
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
]]
|
|
|
|
-- Recycling functions
|
|
local new, del, copy
|
|
--newcount, delcount,createdcount,cached = 0,0,0
|
|
do
|
|
local pool = setmetatable({},{__mode='k'})
|
|
function new()
|
|
--newcount = newcount + 1
|
|
local t = next(pool)
|
|
if t then
|
|
pool[t] = nil
|
|
return t
|
|
else
|
|
--createdcount = createdcount + 1
|
|
return {}
|
|
end
|
|
end
|
|
function copy(t)
|
|
local c = new()
|
|
for k, v in pairs(t) do
|
|
c[k] = v
|
|
end
|
|
return c
|
|
end
|
|
function del(t)
|
|
--delcount = delcount + 1
|
|
for k in pairs(t) do
|
|
t[k] = nil
|
|
end
|
|
pool[t] = true
|
|
end
|
|
-- function cached()
|
|
-- local n = 0
|
|
-- for k in pairs(pool) do
|
|
-- n = n + 1
|
|
-- end
|
|
-- return n
|
|
-- end
|
|
end
|
|
|
|
-- picks the first non-nil value and returns it
|
|
local function pickfirstset(...)
|
|
for i=1,select("#",...) do
|
|
if select(i,...)~=nil then
|
|
return select(i,...)
|
|
end
|
|
end
|
|
end
|
|
|
|
--gets an option from a given group, checking plugins
|
|
local function GetSubOption(group, key)
|
|
if group.plugins then
|
|
for plugin, t in pairs(group.plugins) do
|
|
if t[key] then
|
|
return t[key]
|
|
end
|
|
end
|
|
end
|
|
|
|
return group.args[key]
|
|
end
|
|
|
|
--Option member type definitions, used to decide how to access it
|
|
|
|
--Is the member Inherited from parent options
|
|
local isInherited = {
|
|
set = true,
|
|
get = true,
|
|
func = true,
|
|
confirm = true,
|
|
validate = true,
|
|
disabled = true,
|
|
hidden = true
|
|
}
|
|
|
|
--Does a string type mean a literal value, instead of the default of a method of the handler
|
|
local stringIsLiteral = {
|
|
name = true,
|
|
desc = true,
|
|
icon = true,
|
|
usage = true,
|
|
width = true,
|
|
image = true,
|
|
fontSize = true,
|
|
}
|
|
|
|
--Is Never a function or method
|
|
local allIsLiteral = {
|
|
type = true,
|
|
descStyle = true,
|
|
imageWidth = true,
|
|
imageHeight = true,
|
|
}
|
|
|
|
--gets the value for a member that could be a function
|
|
--function refs are called with an info arg
|
|
--every other type is returned
|
|
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
|
|
member = group[membername]
|
|
end
|
|
for i = 1, #path do
|
|
group = GetSubOption(group, path[i])
|
|
if group[membername] ~= nil then
|
|
member = group[membername]
|
|
end
|
|
end
|
|
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 info = new()
|
|
--traverse the options table, picking up the handler and filling the info with the path
|
|
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.arg = option.arg
|
|
info.handler = handler
|
|
info.option = option
|
|
info.type = option.type
|
|
info.uiType = 'dialog'
|
|
info.uiName = MAJOR
|
|
|
|
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
|
|
a,b,c,d = member(info, ...)
|
|
else
|
|
--Call the method
|
|
if handler and handler[member] then
|
|
a,b,c,d = handler[member](handler, info, ...)
|
|
else
|
|
error(format("Method %s doesn't exist in handler for type %s", member, membername))
|
|
end
|
|
end
|
|
del(info)
|
|
return a,b,c,d
|
|
else
|
|
--The value isnt a function to call, return it
|
|
return member
|
|
end
|
|
end
|
|
|
|
--[[calls an options function that could be inherited, method name or function ref
|
|
local function CallOptionsFunction(funcname ,option, options, path, appName, ...)
|
|
local info = new()
|
|
|
|
local func
|
|
local group = options
|
|
local handler
|
|
|
|
--build the info table containing the path
|
|
-- pick up functions while traversing the tree
|
|
if group[funcname] ~= nil then
|
|
func = group[funcname]
|
|
end
|
|
handler = group.handler or handler
|
|
|
|
for i, v in ipairs(path) do
|
|
group = GetSubOption(group, v)
|
|
info[i] = v
|
|
if group[funcname] ~= nil then
|
|
func = group[funcname]
|
|
end
|
|
handler = group.handler or handler
|
|
end
|
|
|
|
info.options = options
|
|
info[0] = appName
|
|
info.arg = option.arg
|
|
|
|
local a, b, c ,d
|
|
if type(func) == "string" then
|
|
if handler and handler[func] then
|
|
a,b,c,d = handler[func](handler, info, ...)
|
|
else
|
|
error(string.format("Method %s doesn't exist in handler for type func", func))
|
|
end
|
|
elseif type(func) == "function" then
|
|
a,b,c,d = func(info, ...)
|
|
end
|
|
del(info)
|
|
return a,b,c,d
|
|
end
|
|
--]]
|
|
|
|
--tables to hold orders and names for options being sorted, will be created with new()
|
|
--prevents needing to call functions repeatedly while sorting
|
|
local tempOrders
|
|
local tempNames
|
|
|
|
local function compareOptions(a,b)
|
|
if not a then
|
|
return true
|
|
end
|
|
if not b then
|
|
return false
|
|
end
|
|
local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100
|
|
if OrderA == OrderB then
|
|
local NameA = (type(tempNames[a] == "string") and tempNames[a]) or ""
|
|
local NameB = (type(tempNames[b] == "string") and tempNames[b]) or ""
|
|
return NameA:upper() < NameB:upper()
|
|
end
|
|
if OrderA < 0 then
|
|
if OrderB > 0 then
|
|
return false
|
|
end
|
|
else
|
|
if OrderB < 0 then
|
|
return true
|
|
end
|
|
end
|
|
return OrderA < OrderB
|
|
end
|
|
|
|
|
|
|
|
--builds 2 tables out of an options group
|
|
-- keySort, sorted keys
|
|
-- opts, combined options from .plugins and args
|
|
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
|
|
if not opts[k] then
|
|
tinsert(keySort, k)
|
|
opts[k] = v
|
|
|
|
path[#path+1] = k
|
|
tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
|
|
tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
|
|
path[#path] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for k, v in pairs(group.args) do
|
|
if not opts[k] then
|
|
tinsert(keySort, k)
|
|
opts[k] = v
|
|
|
|
path[#path+1] = k
|
|
tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
|
|
tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
|
|
path[#path] = nil
|
|
end
|
|
end
|
|
|
|
tsort(keySort, compareOptions)
|
|
|
|
del(tempOrders)
|
|
del(tempNames)
|
|
end
|
|
|
|
local function DelTree(tree)
|
|
if tree.children then
|
|
local childs = tree.children
|
|
for i = 1, #childs do
|
|
DelTree(childs[i])
|
|
del(childs[i])
|
|
end
|
|
del(childs)
|
|
end
|
|
end
|
|
|
|
local function CleanUserData(widget, event)
|
|
|
|
local user = widget:GetUserDataTable()
|
|
|
|
if user.path then
|
|
del(user.path)
|
|
end
|
|
|
|
if widget.type == "TreeGroup" then
|
|
local tree = user.tree
|
|
widget:SetTree(nil)
|
|
if tree then
|
|
for i = 1, #tree do
|
|
DelTree(tree[i])
|
|
del(tree[i])
|
|
end
|
|
del(tree)
|
|
end
|
|
end
|
|
|
|
if widget.type == "TabGroup" then
|
|
widget:SetTabs(nil)
|
|
if user.tablist then
|
|
del(user.tablist)
|
|
end
|
|
end
|
|
|
|
if widget.type == "DropdownGroup" then
|
|
widget:SetGroupList(nil)
|
|
if user.grouplist then
|
|
del(user.grouplist)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- - 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
|
|
function AceConfigDialog:GetStatusTable(appName, path)
|
|
local status = self.Status
|
|
|
|
if not status[appName] then
|
|
status[appName] = {}
|
|
status[appName].status = {}
|
|
status[appName].children = {}
|
|
end
|
|
|
|
status = status[appName]
|
|
|
|
if path then
|
|
for i = 1, #path do
|
|
local v = path[i]
|
|
if not status.children[v] then
|
|
status.children[v] = {}
|
|
status.children[v].status = {}
|
|
status.children[v].children = {}
|
|
end
|
|
status = status.children[v]
|
|
end
|
|
end
|
|
|
|
return status.status
|
|
end
|
|
|
|
--- Selects the specified path in the options window.
|
|
-- The path specified has to match the keys of the groups in the table.
|
|
-- @param appName The application name as given to `:RegisterOptionsTable()`
|
|
-- @param ... The path to the key that should be selected
|
|
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)
|
|
end
|
|
local options = app("dialog", MAJOR)
|
|
local group = options
|
|
local status = self:GetStatusTable(appName, path)
|
|
if not status.groups then
|
|
status.groups = {}
|
|
end
|
|
status = status.groups
|
|
local treevalue
|
|
local treestatus
|
|
|
|
for n = 1, select('#',...) do
|
|
local key = select(n, ...)
|
|
|
|
if group.childGroups == "tab" or group.childGroups == "select" then
|
|
--if this is a tab or select group, select the group
|
|
status.selected = key
|
|
--children of this group are no longer extra levels of a tree
|
|
treevalue = nil
|
|
else
|
|
--tree group by default
|
|
if treevalue then
|
|
--this is an extra level of a tree group, build a uniquevalue for it
|
|
treevalue = treevalue.."\001"..key
|
|
else
|
|
--this is the top level of a tree group, the uniquevalue is the same as the key
|
|
treevalue = key
|
|
if not status.groups then
|
|
status.groups = {}
|
|
end
|
|
--save this trees status table for any extra levels or groups
|
|
treestatus = status
|
|
end
|
|
--make sure that the tree entry is open, and select it.
|
|
--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
|
|
break
|
|
end
|
|
tinsert(path, key)
|
|
status = self:GetStatusTable(appName, path)
|
|
if not status.groups then
|
|
status.groups = {}
|
|
end
|
|
status = status.groups
|
|
end
|
|
|
|
del(path)
|
|
reg:NotifyChange(appName)
|
|
end
|
|
|
|
local function OptionOnMouseOver(widget, event)
|
|
--show a tooltip/set the status bar to the desc text
|
|
local user = widget:GetUserDataTable()
|
|
local opt = user.option
|
|
local options = user.options
|
|
local path = user.path
|
|
local appName = user.appName
|
|
|
|
GameTooltip: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, 1)
|
|
|
|
if opt.type == 'multiselect' then
|
|
GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1)
|
|
end
|
|
if type(desc) == "string" then
|
|
GameTooltip:AddLine(desc, 1, 1, 1, 1)
|
|
end
|
|
if type(usage) == "string" then
|
|
GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
|
|
end
|
|
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
local function OptionOnMouseLeave(widget, event)
|
|
GameTooltip:Hide()
|
|
end
|
|
|
|
local function GetFuncName(option)
|
|
local type = option.type
|
|
if type == 'execute' then
|
|
return 'func'
|
|
else
|
|
return 'set'
|
|
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
|
|
local dialog, oldstrata
|
|
t.OnAccept = function()
|
|
safecall(func, unpack(t))
|
|
if dialog and oldstrata then
|
|
dialog:SetFrameStrata(oldstrata)
|
|
end
|
|
AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
|
|
del(info)
|
|
end
|
|
t.OnCancel = function()
|
|
if dialog and oldstrata then
|
|
dialog:SetFrameStrata(oldstrata)
|
|
end
|
|
AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
|
|
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
|
|
|
|
local function ActivateControl(widget, event, ...)
|
|
--This function will call the set / execute handler for the widget
|
|
--widget:GetUserDataTable() contains the needed info
|
|
local user = widget:GetUserDataTable()
|
|
local option = user.option
|
|
local options = user.options
|
|
local path = user.path
|
|
local info = new()
|
|
|
|
local func
|
|
local group = options
|
|
local funcname = GetFuncName(option)
|
|
local handler
|
|
local confirm
|
|
local validate
|
|
--build the info table containing the path
|
|
-- pick up functions while traversing the tree
|
|
if group[funcname] ~= nil then
|
|
func = group[funcname]
|
|
end
|
|
handler = group.handler or handler
|
|
confirm = group.confirm
|
|
validate = group.validate
|
|
for i = 1, #path do
|
|
local v = path[i]
|
|
group = GetSubOption(group, v)
|
|
info[i] = v
|
|
if group[funcname] ~= nil then
|
|
func = group[funcname]
|
|
end
|
|
handler = group.handler or handler
|
|
if group.confirm ~= nil then
|
|
confirm = group.confirm
|
|
end
|
|
if group.validate ~= nil then
|
|
validate = group.validate
|
|
end
|
|
end
|
|
|
|
info.options = options
|
|
info.appName = user.appName
|
|
info.arg = option.arg
|
|
info.handler = handler
|
|
info.option = option
|
|
info.type = option.type
|
|
info.uiType = 'dialog'
|
|
info.uiName = MAJOR
|
|
|
|
local name
|
|
if type(option.name) == "function" then
|
|
name = option.name(info)
|
|
elseif type(option.name) == "string" then
|
|
name = option.name
|
|
else
|
|
name = ""
|
|
end
|
|
local usage = option.usage
|
|
local pattern = option.pattern
|
|
|
|
local validated = true
|
|
|
|
if option.type == "input" then
|
|
if type(pattern)=="string" then
|
|
if not strmatch(..., pattern) then
|
|
validated = false
|
|
end
|
|
end
|
|
end
|
|
|
|
local success
|
|
if validated and option.type ~= "execute" then
|
|
if type(validate) == "string" then
|
|
if handler and handler[validate] then
|
|
success, validated = safecall(handler[validate], handler, info, ...)
|
|
if not success then validated = false end
|
|
else
|
|
error(format("Method %s doesn't exist in handler for type execute", validate))
|
|
end
|
|
elseif type(validate) == "function" then
|
|
success, validated = safecall(validate, info, ...)
|
|
if not success then validated = false end
|
|
end
|
|
end
|
|
|
|
local rootframe = user.rootframe
|
|
if type(validated) == "string" then
|
|
--validate function returned a message to display
|
|
if rootframe.SetStatusText then
|
|
rootframe:SetStatusText(validated)
|
|
else
|
|
-- TODO: do something else.
|
|
end
|
|
PlaySound("igPlayerInviteDecline")
|
|
del(info)
|
|
return true
|
|
elseif not validated then
|
|
--validate returned false
|
|
if rootframe.SetStatusText then
|
|
if usage then
|
|
rootframe:SetStatusText(name..": "..usage)
|
|
else
|
|
if pattern then
|
|
rootframe:SetStatusText(name..": Expected "..pattern)
|
|
else
|
|
rootframe:SetStatusText(name..": Invalid Value")
|
|
end
|
|
end
|
|
else
|
|
-- TODO: do something else
|
|
end
|
|
PlaySound("igPlayerInviteDecline")
|
|
del(info)
|
|
return true
|
|
else
|
|
|
|
local confirmText = option.confirmText
|
|
--call confirm func/method
|
|
if type(confirm) == "string" then
|
|
if handler and handler[confirm] then
|
|
success, confirm = safecall(handler[confirm], handler, info, ...)
|
|
if success and type(confirm) == "string" then
|
|
confirmText = confirm
|
|
confirm = true
|
|
elseif not success then
|
|
confirm = false
|
|
end
|
|
else
|
|
error(format("Method %s doesn't exist in handler for type confirm", confirm))
|
|
end
|
|
elseif type(confirm) == "function" then
|
|
success, confirm = safecall(confirm, info, ...)
|
|
if success and type(confirm) == "string" then
|
|
confirmText = confirm
|
|
confirm = true
|
|
elseif not success then
|
|
confirm = false
|
|
end
|
|
end
|
|
|
|
--confirm if needed
|
|
if type(confirm) == "boolean" then
|
|
if confirm then
|
|
if not confirmText then
|
|
local name, desc = option.name, option.desc
|
|
if type(name) == "function" then
|
|
name = name(info)
|
|
end
|
|
if type(desc) == "function" then
|
|
desc = desc(info)
|
|
end
|
|
confirmText = name
|
|
if desc then
|
|
confirmText = confirmText.." - "..desc
|
|
end
|
|
end
|
|
|
|
local iscustom = user.rootframe:GetUserData('iscustom')
|
|
local rootframe
|
|
|
|
if iscustom then
|
|
rootframe = user.rootframe
|
|
end
|
|
local basepath = user.rootframe:GetUserData('basepath')
|
|
if type(func) == "string" then
|
|
if handler and handler[func] then
|
|
confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...)
|
|
else
|
|
error(format("Method %s doesn't exist in handler for type func", func))
|
|
end
|
|
elseif type(func) == "function" then
|
|
confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...)
|
|
end
|
|
--func will be called and info deleted when the confirm dialog is responded to
|
|
return
|
|
end
|
|
end
|
|
|
|
--call the function
|
|
if type(func) == "string" then
|
|
if handler and handler[func] then
|
|
safecall(handler[func],handler, info, ...)
|
|
else
|
|
error(format("Method %s doesn't exist in handler for type func", func))
|
|
end
|
|
elseif type(func) == "function" then
|
|
safecall(func,info, ...)
|
|
end
|
|
|
|
|
|
|
|
local iscustom = user.rootframe:GetUserData('iscustom')
|
|
local basepath = user.rootframe:GetUserData('basepath') or emptyTbl
|
|
--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
|
|
AceConfigDialog:Open(user.appName, unpack(basepath))
|
|
end
|
|
end
|
|
elseif option.type == "range" then
|
|
if event == "OnMouseUp" then
|
|
if iscustom then
|
|
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
|
|
else
|
|
AceConfigDialog:Open(user.appName, unpack(basepath))
|
|
end
|
|
end
|
|
--multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed'
|
|
elseif option.type == "multiselect" then
|
|
user.valuechanged = true
|
|
else
|
|
if iscustom then
|
|
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
|
|
else
|
|
AceConfigDialog:Open(user.appName, unpack(basepath))
|
|
end
|
|
end
|
|
|
|
end
|
|
del(info)
|
|
end
|
|
|
|
local function ActivateSlider(widget, event, value)
|
|
local option = widget:GetUserData('option')
|
|
local min, max, step = option.min or (not option.softMin and 0 or nil), option.max or (not option.softMax and 100 or nil), option.step
|
|
if min then
|
|
if step then
|
|
value = math_floor((value - min) / step + 0.5) * step + min
|
|
end
|
|
value = math_max(value, min)
|
|
end
|
|
if max then
|
|
value = math_min(value, max)
|
|
end
|
|
ActivateControl(widget,event,value)
|
|
end
|
|
|
|
--called from a checkbox that is part of an internally created multiselect group
|
|
--this type is safe to refresh on activation of one control
|
|
local function ActivateMultiControl(widget, event, ...)
|
|
ActivateControl(widget, event, widget:GetUserData('value'), ...)
|
|
local user = widget:GetUserDataTable()
|
|
local iscustom = user.rootframe:GetUserData('iscustom')
|
|
local basepath = user.rootframe:GetUserData('basepath') or emptyTbl
|
|
if iscustom then
|
|
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
|
|
else
|
|
AceConfigDialog:Open(user.appName, unpack(basepath))
|
|
end
|
|
end
|
|
|
|
local function MultiControlOnClosed(widget, event, ...)
|
|
local user = widget:GetUserDataTable()
|
|
if user.valuechanged then
|
|
local iscustom = user.rootframe:GetUserData('iscustom')
|
|
local basepath = user.rootframe:GetUserData('basepath') or emptyTbl
|
|
if iscustom then
|
|
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
|
|
else
|
|
AceConfigDialog:Open(user.appName, unpack(basepath))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function FrameOnClose(widget, event)
|
|
local appName = widget:GetUserData('appName')
|
|
AceConfigDialog.OpenFrames[appName] = nil
|
|
gui:Release(widget)
|
|
end
|
|
|
|
local function CheckOptionHidden(option, options, path, appName)
|
|
--check for a specific boolean option
|
|
local hidden = pickfirstset(option.dialogHidden,option.guiHidden)
|
|
if hidden ~= nil then
|
|
return hidden
|
|
end
|
|
|
|
return GetOptionsMemberValue("hidden", option, options, path, appName)
|
|
end
|
|
|
|
local function CheckOptionDisabled(option, options, path, appName)
|
|
--check for a specific boolean option
|
|
local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled)
|
|
if disabled ~= nil then
|
|
return disabled
|
|
end
|
|
|
|
return GetOptionsMemberValue("disabled", option, options, path, appName)
|
|
end
|
|
--[[
|
|
local function BuildTabs(group, options, path, appName)
|
|
local tabs = new()
|
|
local text = new()
|
|
local keySort = new()
|
|
local opts = new()
|
|
|
|
BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
|
|
|
|
for i = 1, #keySort do
|
|
local k = keySort[i]
|
|
local v = opts[k]
|
|
if v.type == "group" then
|
|
path[#path+1] = k
|
|
local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
|
|
local hidden = CheckOptionHidden(v, options, path, appName)
|
|
if not inline and not hidden then
|
|
tinsert(tabs, k)
|
|
text[k] = GetOptionsMemberValue("name", v, options, path, appName)
|
|
end
|
|
path[#path] = nil
|
|
end
|
|
end
|
|
|
|
del(keySort)
|
|
del(opts)
|
|
|
|
return tabs, text
|
|
end
|
|
]]
|
|
local function BuildSelect(group, options, path, appName)
|
|
local groups = new()
|
|
local keySort = new()
|
|
local opts = new()
|
|
|
|
BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
|
|
|
|
for i = 1, #keySort do
|
|
local k = keySort[i]
|
|
local v = opts[k]
|
|
if v.type == "group" then
|
|
path[#path+1] = k
|
|
local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
|
|
local hidden = CheckOptionHidden(v, options, path, appName)
|
|
if not inline and not hidden then
|
|
groups[k] = GetOptionsMemberValue("name", v, options, path, appName)
|
|
end
|
|
path[#path] = nil
|
|
end
|
|
end
|
|
|
|
del(keySort)
|
|
del(opts)
|
|
|
|
return groups
|
|
end
|
|
|
|
local function BuildSubGroups(group, tree, options, path, appName)
|
|
local keySort = new()
|
|
local opts = new()
|
|
|
|
BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
|
|
|
|
for i = 1, #keySort do
|
|
local k = keySort[i]
|
|
local v = opts[k]
|
|
if v.type == "group" then
|
|
path[#path+1] = k
|
|
local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
|
|
local hidden = CheckOptionHidden(v, options, path, appName)
|
|
if not inline and not hidden then
|
|
local entry = new()
|
|
entry.value = k
|
|
entry.text = GetOptionsMemberValue("name", v, options, path, appName)
|
|
entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
|
|
entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName)
|
|
entry.disabled = CheckOptionDisabled(v, options, path, appName)
|
|
if not tree.children then tree.children = new() end
|
|
tinsert(tree.children,entry)
|
|
if (v.childGroups or "tree") == "tree" then
|
|
BuildSubGroups(v,entry, options, path, appName)
|
|
end
|
|
end
|
|
path[#path] = nil
|
|
end
|
|
end
|
|
|
|
del(keySort)
|
|
del(opts)
|
|
end
|
|
|
|
local function BuildGroups(group, options, path, appName, recurse)
|
|
local tree = new()
|
|
local keySort = new()
|
|
local opts = new()
|
|
|
|
BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
|
|
|
|
for i = 1, #keySort do
|
|
local k = keySort[i]
|
|
local v = opts[k]
|
|
if v.type == "group" then
|
|
path[#path+1] = k
|
|
local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
|
|
local hidden = CheckOptionHidden(v, options, path, appName)
|
|
if not inline and not hidden then
|
|
local entry = new()
|
|
entry.value = k
|
|
entry.text = GetOptionsMemberValue("name", v, options, path, appName)
|
|
entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
|
|
entry.disabled = CheckOptionDisabled(v, options, path, appName)
|
|
tinsert(tree,entry)
|
|
if recurse and (v.childGroups or "tree") == "tree" then
|
|
BuildSubGroups(v,entry, options, path, appName)
|
|
end
|
|
end
|
|
path[#path] = nil
|
|
end
|
|
end
|
|
del(keySort)
|
|
del(opts)
|
|
return tree
|
|
end
|
|
|
|
local function InjectInfo(control, options, option, path, rootframe, appName)
|
|
local user = control:GetUserDataTable()
|
|
for i = 1, #path do
|
|
user[i] = path[i]
|
|
end
|
|
user.rootframe = rootframe
|
|
user.option = option
|
|
user.options = options
|
|
user.path = copy(path)
|
|
user.appName = appName
|
|
control:SetCallback("OnRelease", CleanUserData)
|
|
control:SetCallback("OnLeave", OptionOnMouseLeave)
|
|
control:SetCallback("OnEnter", OptionOnMouseOver)
|
|
end
|
|
|
|
|
|
--[[
|
|
options - root of the options table being fed
|
|
container - widget that controls will be placed in
|
|
rootframe - Frame object the options are in
|
|
path - table with the keys to get to the group being fed
|
|
--]]
|
|
|
|
local function FeedOptions(appName, options,container,rootframe,path,group,inline)
|
|
local keySort = new()
|
|
local opts = new()
|
|
|
|
BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
|
|
|
|
for i = 1, #keySort do
|
|
local k = keySort[i]
|
|
local v = opts[k]
|
|
tinsert(path, k)
|
|
local hidden = CheckOptionHidden(v, options, path, appName)
|
|
local name = GetOptionsMemberValue("name", v, options, path, appName)
|
|
if not hidden then
|
|
if v.type == "group" then
|
|
if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
|
|
--Inline group
|
|
local GroupContainer
|
|
if name and name ~= "" then
|
|
GroupContainer = gui:Create("InlineGroup")
|
|
GroupContainer:SetTitle(name or "")
|
|
else
|
|
GroupContainer = gui:Create("SimpleGroup")
|
|
end
|
|
|
|
GroupContainer.width = "fill"
|
|
GroupContainer:SetLayout("flow")
|
|
container:AddChild(GroupContainer)
|
|
FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
|
|
end
|
|
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' then
|
|
control = gui:Create("Icon")
|
|
if not width then
|
|
width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
|
|
end
|
|
if not height then
|
|
height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
|
|
end
|
|
if type(imageCoords) == 'table' then
|
|
control:SetImage(image, unpack(imageCoords))
|
|
else
|
|
control:SetImage(image)
|
|
end
|
|
if type(width) ~= "number" then
|
|
width = 32
|
|
end
|
|
if type(height) ~= "number" then
|
|
height = 32
|
|
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
|
|
|
|
if v.multiline and control.SetNumLines then
|
|
control:SetNumLines(tonumber(v.multiline) or 4)
|
|
end
|
|
control:SetLabel(name)
|
|
control:SetCallback("OnEnterPressed",ActivateControl)
|
|
local text = GetOptionsMemberValue("get",v, options, path, appName)
|
|
if type(text) ~= "string" then
|
|
text = ""
|
|
end
|
|
control:SetText(text)
|
|
|
|
elseif v.type == "toggle" then
|
|
control = gui:Create("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' then
|
|
if type(imageCoords) == 'table' then
|
|
control:SetImage(image, unpack(imageCoords))
|
|
else
|
|
control:SetImage(image)
|
|
end
|
|
end
|
|
elseif v.type == "range" then
|
|
control = gui:Create("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)
|
|
local value = GetOptionsMemberValue("get",v, options, path, appName)
|
|
if type(value) ~= "number" then
|
|
value = 0
|
|
end
|
|
control:SetValue(value)
|
|
control:SetCallback("OnValueChanged",ActivateSlider)
|
|
control:SetCallback("OnMouseUp",ActivateSlider)
|
|
|
|
elseif v.type == "select" then
|
|
local values = GetOptionsMemberValue("values", v, options, path, appName)
|
|
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:SetLabel(name)
|
|
control:SetList(values)
|
|
local value = GetOptionsMemberValue("get",v, options, path, appName)
|
|
if not values[value] then
|
|
value = nil
|
|
end
|
|
control:SetValue(value)
|
|
control:SetCallback("OnValueChanged",ActivateControl)
|
|
|
|
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
|
|
tinsert(valuesort, value)
|
|
end
|
|
end
|
|
tsort(valuesort)
|
|
|
|
if controlType then
|
|
control = gui:Create(controlType)
|
|
if not control then
|
|
geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
|
|
end
|
|
end
|
|
if control then
|
|
control:SetMultiselect(true)
|
|
control:SetLabel(name)
|
|
control:SetList(values)
|
|
control:SetDisabled(disabled)
|
|
control:SetCallback("OnValueChanged",ActivateControl)
|
|
control:SetCallback("OnClosed", MultiControlOnClosed)
|
|
local width = GetOptionsMemberValue("width",v,options,path,appName)
|
|
if width == "double" then
|
|
control:SetWidth(width_multiplier * 2)
|
|
elseif width == "half" then
|
|
control:SetWidth(width_multiplier / 2)
|
|
elseif width == "full" then
|
|
control.width = "fill"
|
|
else
|
|
control:SetWidth(width_multiplier)
|
|
end
|
|
--check:SetTriState(v.tristate)
|
|
for i = 1, #valuesort do
|
|
local key = valuesort[i]
|
|
local value = GetOptionsMemberValue("get",v, options, path, appName, key)
|
|
control:SetItemValue(key,value)
|
|
end
|
|
else
|
|
control = gui:Create("InlineGroup")
|
|
control:SetLayout("Flow")
|
|
control:SetTitle(name)
|
|
control.width = "fill"
|
|
|
|
control:PauseLayout()
|
|
local width = GetOptionsMemberValue("width",v,options,path,appName)
|
|
for i = 1, #valuesort do
|
|
local value = valuesort[i]
|
|
local text = values[value]
|
|
local check = gui:Create("CheckBox")
|
|
check:SetLabel(text)
|
|
check:SetUserData('value', value)
|
|
check:SetUserData('text', text)
|
|
check:SetDisabled(disabled)
|
|
check:SetTriState(v.tristate)
|
|
check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
|
|
check:SetCallback("OnValueChanged",ActivateMultiControl)
|
|
InjectInfo(check, options, v, path, rootframe, appName)
|
|
control:AddChild(check)
|
|
if width == "double" then
|
|
check:SetWidth(width_multiplier * 2)
|
|
elseif width == "half" then
|
|
check:SetWidth(width_multiplier / 2)
|
|
elseif width == "full" then
|
|
check.width = "fill"
|
|
else
|
|
check:SetWidth(width_multiplier)
|
|
end
|
|
end
|
|
control:ResumeLayout()
|
|
control:DoLayout()
|
|
|
|
|
|
end
|
|
|
|
del(valuesort)
|
|
|
|
elseif v.type == "color" then
|
|
control = gui:Create("ColorPicker")
|
|
control:SetLabel(name)
|
|
control:SetHasAlpha(v.hasAlpha)
|
|
control:SetColor(GetOptionsMemberValue("get",v, options, path, appName))
|
|
control:SetCallback("OnValueChanged",ActivateControl)
|
|
control:SetCallback("OnValueConfirmed",ActivateControl)
|
|
|
|
elseif v.type == "keybinding" then
|
|
control = gui:Create("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:SetText(name)
|
|
control.width = "fill"
|
|
|
|
elseif v.type == "description" then
|
|
control = gui:Create("Label")
|
|
control:SetText(name)
|
|
|
|
local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName)
|
|
if fontSize == "medium" then
|
|
control:SetFontObject(GameFontHighlight)
|
|
elseif fontSize == "large" then
|
|
control:SetFontObject(GameFontHighlightLarge)
|
|
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' then
|
|
if not width then
|
|
width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
|
|
end
|
|
if not height then
|
|
height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
|
|
end
|
|
if type(imageCoords) == 'table' then
|
|
control:SetImage(image, unpack(imageCoords))
|
|
else
|
|
control:SetImage(image)
|
|
end
|
|
if type(width) ~= "number" then
|
|
width = 32
|
|
end
|
|
if type(height) ~= "number" then
|
|
height = 32
|
|
end
|
|
control:SetImageSize(width, height)
|
|
end
|
|
local width = GetOptionsMemberValue("width",v,options,path,appName)
|
|
control.width = not width and "fill"
|
|
end
|
|
|
|
--Common Init
|
|
if control then
|
|
if control.width ~= "fill" then
|
|
local width = GetOptionsMemberValue("width",v,options,path,appName)
|
|
if width == "double" then
|
|
control:SetWidth(width_multiplier * 2)
|
|
elseif width == "half" then
|
|
control:SetWidth(width_multiplier / 2)
|
|
elseif width == "full" then
|
|
control.width = "fill"
|
|
else
|
|
control:SetWidth(width_multiplier)
|
|
end
|
|
end
|
|
if control.SetDisabled then
|
|
local disabled = CheckOptionDisabled(v, options, path, appName)
|
|
control:SetDisabled(disabled)
|
|
end
|
|
|
|
InjectInfo(control, options, v, path, rootframe, appName)
|
|
container:AddChild(control)
|
|
end
|
|
|
|
end
|
|
end
|
|
tremove(path)
|
|
end
|
|
container:ResumeLayout()
|
|
container:DoLayout()
|
|
del(keySort)
|
|
del(opts)
|
|
end
|
|
|
|
local function BuildPath(path, ...)
|
|
for i = 1, select('#',...) do
|
|
tinsert(path, (select(i,...)))
|
|
end
|
|
end
|
|
|
|
|
|
local function TreeOnButtonEnter(widget, event, uniquevalue, button)
|
|
local user = widget:GetUserDataTable()
|
|
if not user then return end
|
|
local options = user.options
|
|
local option = user.option
|
|
local path = user.path
|
|
local appName = user.appName
|
|
|
|
local feedpath = new()
|
|
for i = 1, #path do
|
|
feedpath[i] = path[i]
|
|
end
|
|
|
|
BuildPath(feedpath, ("\001"):split(uniquevalue))
|
|
local group = options
|
|
for i = 1, #feedpath do
|
|
if not group then return end
|
|
group = GetSubOption(group, feedpath[i])
|
|
end
|
|
|
|
local name = GetOptionsMemberValue("name", group, options, feedpath, appName)
|
|
local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName)
|
|
|
|
GameTooltip:SetOwner(button, "ANCHOR_NONE")
|
|
if widget.type == "TabGroup" then
|
|
GameTooltip:SetPoint("BOTTOM",button,"TOP")
|
|
else
|
|
GameTooltip:SetPoint("LEFT",button,"RIGHT")
|
|
end
|
|
|
|
GameTooltip:SetText(name, 1, .82, 0, 1)
|
|
|
|
if type(desc) == "string" then
|
|
GameTooltip:AddLine(desc, 1, 1, 1, 1)
|
|
end
|
|
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
local function TreeOnButtonLeave(widget, event, value, button)
|
|
GameTooltip: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
|
|
del(feedpath)
|
|
del(temppath)
|
|
return false
|
|
end
|
|
end
|
|
del(feedpath)
|
|
del(temppath)
|
|
return true
|
|
end
|
|
|
|
local function GroupSelected(widget, event, uniquevalue)
|
|
|
|
local user = widget:GetUserDataTable()
|
|
|
|
local options = user.options
|
|
local option = user.option
|
|
local path = user.path
|
|
local rootframe = user.rootframe
|
|
|
|
local feedpath = new()
|
|
for i = 1, #path do
|
|
feedpath[i] = path[i]
|
|
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)
|
|
|
|
del(feedpath)
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
-- INTERNAL --
|
|
This function will feed one group, and any inline child groups into the given container
|
|
Select Groups will only have the selection control (tree, tabs, dropdown) fed in
|
|
and have a group selected, this event will trigger the feeding of child groups
|
|
|
|
Rules:
|
|
If the group is Inline, FeedOptions
|
|
If the group has no child groups, FeedOptions
|
|
|
|
If the group is a tab or select group, FeedOptions then add the Group Control
|
|
If the group is a tree group FeedOptions then
|
|
its parent isnt a tree group: then add the tree control containing this and all child tree groups
|
|
if its parent is a tree group, its already a node on a tree
|
|
--]]
|
|
|
|
function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot)
|
|
local group = options
|
|
--follow the path to get to the curent group
|
|
local inline
|
|
local grouptype, parenttype = options.childGroups, "none"
|
|
|
|
|
|
for i = 1, #path do
|
|
local v = path[i]
|
|
group = GetSubOption(group, v)
|
|
inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
|
|
parenttype = grouptype
|
|
grouptype = group.childGroups
|
|
end
|
|
|
|
if not parenttype then
|
|
parenttype = "tree"
|
|
end
|
|
|
|
--check if the group has child groups
|
|
local hasChildGroups
|
|
for k, v in pairs(group.args) do
|
|
if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
|
|
hasChildGroups = true
|
|
end
|
|
end
|
|
if group.plugins then
|
|
for plugin, t in pairs(group.plugins) do
|
|
for k, v in pairs(t) do
|
|
if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
|
|
hasChildGroups = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
container:SetLayout("flow")
|
|
local scroll
|
|
|
|
--Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on
|
|
if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and (parenttype == "tree" and not isRoot)) then
|
|
if container.type ~= "InlineGroup" and container.type ~= "SimpleGroup" then
|
|
scroll = gui:Create("ScrollFrame")
|
|
scroll:SetLayout("flow")
|
|
scroll.width = "fill"
|
|
scroll.height = "fill"
|
|
container:SetLayout("fill")
|
|
container:AddChild(scroll)
|
|
container = scroll
|
|
end
|
|
end
|
|
|
|
FeedOptions(appName,options,container,rootframe,path,group,nil)
|
|
|
|
if scroll then
|
|
container:PerformLayout()
|
|
local status = self:GetStatusTable(appName, path)
|
|
if not status.scroll then
|
|
status.scroll = {}
|
|
end
|
|
scroll:SetStatusTable(status.scroll)
|
|
end
|
|
|
|
if hasChildGroups and not inline then
|
|
local name = GetOptionsMemberValue("name", group, options, path, appName)
|
|
if grouptype == "tab" then
|
|
|
|
local tab = gui:Create("TabGroup")
|
|
InjectInfo(tab, options, group, path, rootframe, appName)
|
|
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 = {}
|
|
end
|
|
tab:SetStatusTable(status.groups)
|
|
tab.width = "fill"
|
|
tab.height = "fill"
|
|
|
|
local tabs = BuildGroups(group, options, path, appName)
|
|
tab:SetTabs(tabs)
|
|
tab:SetUserData("tablist", tabs)
|
|
|
|
for i = 1, #tabs do
|
|
local entry = tabs[i]
|
|
if not entry.disabled then
|
|
tab:SelectTab((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
|
|
break
|
|
end
|
|
end
|
|
|
|
container:AddChild(tab)
|
|
|
|
elseif grouptype == "select" then
|
|
|
|
local select = gui:Create("DropdownGroup")
|
|
select:SetTitle(name)
|
|
InjectInfo(select, options, group, path, rootframe, appName)
|
|
select:SetCallback("OnGroupSelected", GroupSelected)
|
|
local status = AceConfigDialog:GetStatusTable(appName, path)
|
|
if not status.groups then
|
|
status.groups = {}
|
|
end
|
|
select:SetStatusTable(status.groups)
|
|
local grouplist = BuildSelect(group, options, path, appName)
|
|
select:SetGroupList(grouplist)
|
|
select:SetUserData("grouplist", grouplist)
|
|
local firstgroup
|
|
for k, v in pairs(grouplist) do
|
|
if not firstgroup or k < firstgroup then
|
|
firstgroup = k
|
|
end
|
|
end
|
|
|
|
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"
|
|
|
|
container:AddChild(select)
|
|
|
|
--assume tree group by default
|
|
--if parenttype is tree then this group is already a node on that tree
|
|
elseif (parenttype ~= "tree") or isRoot then
|
|
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
|
|
local treedefinition = BuildGroups(group, options, path, appName, true)
|
|
tree:SetStatusTable(status.groups)
|
|
|
|
tree:SetTree(treedefinition)
|
|
tree:SetUserData("tree",treedefinition)
|
|
|
|
for i = 1, #treedefinition do
|
|
local entry = treedefinition[i]
|
|
if not entry.disabled then
|
|
tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
|
|
break
|
|
end
|
|
end
|
|
|
|
container:AddChild(tree)
|
|
end
|
|
end
|
|
end
|
|
|
|
local old_CloseSpecialWindows
|
|
|
|
|
|
local function RefreshOnUpdate(this)
|
|
for appName in pairs(this.closing) do
|
|
if AceConfigDialog.OpenFrames[appName] then
|
|
AceConfigDialog.OpenFrames[appName]:Hide()
|
|
end
|
|
if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
|
|
for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
|
|
if not widget:IsVisible() then
|
|
widget:ReleaseChildren()
|
|
end
|
|
end
|
|
end
|
|
this.closing[appName] = nil
|
|
end
|
|
|
|
if this.closeAll then
|
|
for k, v in pairs(AceConfigDialog.OpenFrames) do
|
|
v:Hide()
|
|
end
|
|
this.closeAll = nil
|
|
end
|
|
|
|
for appName in pairs(this.apps) do
|
|
if AceConfigDialog.OpenFrames[appName] then
|
|
local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable()
|
|
AceConfigDialog:Open(appName, unpack(user.basepath or emptyTbl))
|
|
end
|
|
if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
|
|
for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
|
|
local user = widget:GetUserDataTable()
|
|
if widget:IsVisible() then
|
|
AceConfigDialog:Open(widget:GetUserData('appName'), widget, unpack(user.basepath or emptyTbl))
|
|
end
|
|
end
|
|
end
|
|
this.apps[appName] = nil
|
|
end
|
|
this:SetScript("OnUpdate", nil)
|
|
end
|
|
|
|
-- Upgrade the OnUpdate script as well, if needed.
|
|
if AceConfigDialog.frame:GetScript("OnUpdate") then
|
|
AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
|
end
|
|
|
|
--- Close all open options windows
|
|
function AceConfigDialog:CloseAll()
|
|
AceConfigDialog.frame.closeAll = true
|
|
AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
|
if next(self.OpenFrames) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
--- Close a specific options window.
|
|
-- @param appName The application name as given to `:RegisterOptionsTable()`
|
|
function AceConfigDialog:Close(appName)
|
|
if self.OpenFrames[appName] then
|
|
AceConfigDialog.frame.closing[appName] = true
|
|
AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- Internal -- Called by AceConfigRegistry
|
|
function AceConfigDialog:ConfigTableChanged(event, appName)
|
|
AceConfigDialog.frame.apps[appName] = true
|
|
AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
|
end
|
|
|
|
reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged")
|
|
|
|
--- Sets the default size of the options window for a specific application.
|
|
-- @param appName The application name as given to `:RegisterOptionsTable()`
|
|
-- @param width The default width
|
|
-- @param height The default height
|
|
function AceConfigDialog:SetDefaultSize(appName, width, height)
|
|
local status = AceConfigDialog:GetStatusTable(appName)
|
|
if type(width) == "number" and type(height) == "number" then
|
|
status.width = width
|
|
status.height = height
|
|
end
|
|
end
|
|
|
|
--- Open an option window at the specified path (if any).
|
|
-- This function can optionally feed the group into a pre-created container
|
|
-- instead of creating a new container frame.
|
|
-- @paramsig appName [, container][, ...]
|
|
-- @param appName The application name as given to `:RegisterOptionsTable()`
|
|
-- @param container An optional container frame to feed the options into
|
|
-- @param ... The path to open after creating the options window (see `:SelectGroup` for details)
|
|
function AceConfigDialog:Open(appName, container, ...)
|
|
if not old_CloseSpecialWindows then
|
|
old_CloseSpecialWindows = CloseSpecialWindows
|
|
CloseSpecialWindows = function()
|
|
local found = old_CloseSpecialWindows()
|
|
return self:CloseAll() or found
|
|
end
|
|
end
|
|
local app = reg:GetOptionsTable(appName)
|
|
if not app then
|
|
error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
|
|
end
|
|
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
|
|
tinsert(path, container)
|
|
container = nil
|
|
end
|
|
for n = 1, select('#',...) do
|
|
tinsert(path, (select(n, ...)))
|
|
end
|
|
|
|
--if a container is given feed into that
|
|
if container then
|
|
f = container
|
|
f:ReleaseChildren()
|
|
f:SetUserData('appName', appName)
|
|
f:SetUserData('iscustom', true)
|
|
if #path > 0 then
|
|
f:SetUserData('basepath', copy(path))
|
|
end
|
|
local status = AceConfigDialog:GetStatusTable(appName)
|
|
if not status.width then
|
|
status.width = 700
|
|
end
|
|
if not status.height then
|
|
status.height = 500
|
|
end
|
|
if f.SetStatusTable then
|
|
f:SetStatusTable(status)
|
|
end
|
|
if f.SetTitle then
|
|
f:SetTitle(name or "")
|
|
end
|
|
else
|
|
if not self.OpenFrames[appName] then
|
|
f = gui:Create("Frame")
|
|
self.OpenFrames[appName] = f
|
|
else
|
|
f = self.OpenFrames[appName]
|
|
end
|
|
f:ReleaseChildren()
|
|
f:SetCallback("OnClose", FrameOnClose)
|
|
f:SetUserData('appName', appName)
|
|
if #path > 0 then
|
|
f:SetUserData('basepath', copy(path))
|
|
end
|
|
f:SetTitle(name or "")
|
|
local status = AceConfigDialog:GetStatusTable(appName)
|
|
f:SetStatusTable(status)
|
|
end
|
|
|
|
self:FeedGroup(appName,options,f,f,path,true)
|
|
if f.Show then
|
|
f:Show()
|
|
end
|
|
del(path)
|
|
end
|
|
|
|
-- convert pre-39 BlizOptions structure to the new format
|
|
if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then
|
|
local old = AceConfigDialog.BlizOptions
|
|
local new = {}
|
|
for key, widget in pairs(old) do
|
|
local appName = widget:GetUserData('appName')
|
|
if not new[appName] then new[appName] = {} end
|
|
new[appName][key] = widget
|
|
end
|
|
AceConfigDialog.BlizOptions = new
|
|
else
|
|
AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {}
|
|
end
|
|
|
|
local function FeedToBlizPanel(widget, event)
|
|
local path = widget:GetUserData('path')
|
|
AceConfigDialog:Open(widget:GetUserData('appName'), widget, unpack(path or emptyTbl))
|
|
end
|
|
|
|
local function ClearBlizPanel(widget, event)
|
|
local appName = widget:GetUserData('appName')
|
|
AceConfigDialog.frame.closing[appName] = true
|
|
AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
|
end
|
|
|
|
--- Add an option table into the Blizzard Interface Options panel.
|
|
-- You can optionally supply a descriptive name to use and a parent frame to use,
|
|
-- as well as a path in the options table.\\
|
|
-- If no name is specified, the appName will be used instead.
|
|
--
|
|
-- If you specify a proper `parent` (by name), the interface options will generate a
|
|
-- tree layout. Note that only one level of children is supported, so the parent always
|
|
-- has to be a head-level note.
|
|
--
|
|
-- This function returns a reference to the container frame registered with the Interface
|
|
-- Options. You can use this reference to open the options with the API function
|
|
-- `InterfaceOptionsFrame_OpenToCategory`.
|
|
-- @param appName The application name as given to `:RegisterOptionsTable()`
|
|
-- @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.
|
|
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
|
|
group:SetName(name or appName, parent)
|
|
|
|
group:SetTitle(name or appName)
|
|
group:SetUserData('appName', appName)
|
|
if select('#', ...) > 0 then
|
|
local path = {}
|
|
for n = 1, select('#',...) do
|
|
tinsert(path, (select(n, ...)))
|
|
end
|
|
group:SetUserData('path', path)
|
|
end
|
|
group:SetCallback("OnShow", FeedToBlizPanel)
|
|
group:SetCallback("OnHide", ClearBlizPanel)
|
|
InterfaceOptions_AddCategory(group.frame)
|
|
return group.frame
|
|
else
|
|
error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2)
|
|
end
|
|
end
|