1 --- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables.
3 -- @name AceConfigDialog-3.0
4 -- @release $Id: AceConfigDialog-3.0.lua 1089 2013-09-13 14:32:35Z nevcairiel $
6 local LibStub = LibStub
7 local MAJOR, MINOR = "AceConfigDialog-3.0", 58
8 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
10 if not AceConfigDialog then return end
12 AceConfigDialog.OpenFrames = AceConfigDialog.OpenFrames or {}
13 AceConfigDialog.Status = AceConfigDialog.Status or {}
14 AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame")
16 AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {}
17 AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
18 AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {}
20 local gui = LibStub("AceGUI-3.0")
21 local reg = LibStub("AceConfigRegistry-3.0")
24 local tconcat, tinsert, tsort, tremove, tsort = table.concat, table.insert, table.sort, table.remove, table.sort
25 local strmatch, format = string.match, string.format
26 local assert, loadstring, error = assert, loadstring, error
27 local pairs, next, select, type, unpack, wipe, ipairs = pairs, next, select, type, unpack, wipe, ipairs
28 local rawset, tostring, tonumber = rawset, tostring, tonumber
29 local math_min, math_max, math_floor = math.min, math.max, math.floor
31 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
32 -- List them here for Mikk's FindGlobals script
33 -- GLOBALS: NORMAL_FONT_COLOR, GameTooltip, StaticPopupDialogs, ACCEPT, CANCEL, StaticPopup_Show
34 -- GLOBALS: PlaySound, GameFontHighlight, GameFontHighlightSmall, GameFontHighlightLarge
35 -- GLOBALS: CloseSpecialWindows, InterfaceOptions_AddCategory, geterrorhandler
40 xpcall safecall implementation
44 local function errorhandler(err)
45 return geterrorhandler()(err)
48 local function CreateDispatcher(argCount)
50 local xpcall, eh = ...
52 local function call() return method(ARGS) end
54 local function dispatch(func, ...)
56 if not method then return end
58 return xpcall(call, eh)
65 for i = 1, argCount do ARGS[i] = "arg"..i end
66 code = code:gsub("ARGS", tconcat(ARGS, ", "))
67 return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
70 local Dispatchers = setmetatable({}, {__index=function(self, argCount)
71 local dispatcher = CreateDispatcher(argCount)
72 rawset(self, argCount, dispatcher)
75 Dispatchers[0] = function(func)
76 return xpcall(func, errorhandler)
79 local function safecall(func, ...)
80 return Dispatchers[select("#", ...)](func, ...)
83 local width_multiplier = 170
87 Tree - All Descendant Groups will all become nodes on the tree, direct child options will appear above the tree
88 - Descendant Groups with inline=true and thier children will not become nodes
90 Tab - Direct Child Groups will become tabs, direct child options will appear above the tab control
91 - Grandchild groups will default to inline unless specified otherwise
93 Select- Same as Tab but with entries in a dropdown rather than tabs
97 - Will not become nodes of a select group, they will be effectivly part of thier parent group seperated by a border
98 - If declared on a direct child of a root node of a select group, they will appear above the group container control
99 - When a group is displayed inline, all descendants will also be inline members of the group
103 -- Recycling functions
105 --newcount, delcount,createdcount,cached = 0,0,0
107 local pool = setmetatable({},{__mode="k"})
109 --newcount = newcount + 1
115 --createdcount = createdcount + 1
121 for k, v in pairs(t) do
127 --delcount = delcount + 1
133 -- for k in pairs(pool) do
140 -- picks the first non-nil value and returns it
141 local function pickfirstset(...)
142 for i=1,select("#",...) do
143 if select(i,...)~=nil then
149 --gets an option from a given group, checking plugins
150 local function GetSubOption(group, key)
151 if group.plugins then
152 for plugin, t in pairs(group.plugins) do
159 return group.args[key]
162 --Option member type definitions, used to decide how to access it
164 --Is the member Inherited from parent options
165 local isInherited = {
175 --Does a string type mean a literal value, instead of the default of a method of the handler
176 local stringIsLiteral = {
186 --Is Never a function or method
187 local allIsLiteral = {
194 --gets the value for a member that could be a function
195 --function refs are called with an info arg
196 --every other type is returned
197 local function GetOptionsMemberValue(membername, option, options, path, appName, ...)
198 --get definition for the member
199 local inherits = isInherited[membername]
202 --get the member of the option, traversing the tree if it can be inherited
206 local group = options
207 if group[membername] ~= nil then
208 member = group[membername]
211 group = GetSubOption(group, path[i])
212 if group[membername] ~= nil then
213 member = group[membername]
217 member = option[membername]
220 --check if we need to call a functon, or if we have a literal value
221 if ( not allIsLiteral[membername] ) and ( type(member) == "function" or ((not stringIsLiteral[membername]) and type(member) == "string") ) then
222 --We have a function to call
224 --traverse the options table, picking up the handler and filling the info with the path
226 local group = options
227 handler = group.handler or handler
230 group = GetSubOption(group, path[i])
232 handler = group.handler or handler
235 info.options = options
236 info.appName = appName
238 info.arg = option.arg
239 info.handler = handler
241 info.type = option.type
242 info.uiType = "dialog"
246 --using 4 returns for the get of a color type, increase if a type needs more
247 if type(member) == "function" then
249 a,b,c,d = member(info, ...)
252 if handler and handler[member] then
253 a,b,c,d = handler[member](handler, info, ...)
255 error(format("Method %s doesn't exist in handler for type %s", member, membername))
261 --The value isnt a function to call, return it
266 --[[calls an options function that could be inherited, method name or function ref
267 local function CallOptionsFunction(funcname ,option, options, path, appName, ...)
271 local group = options
274 --build the info table containing the path
275 -- pick up functions while traversing the tree
276 if group[funcname] ~= nil then
277 func = group[funcname]
279 handler = group.handler or handler
281 for i, v in ipairs(path) do
282 group = GetSubOption(group, v)
284 if group[funcname] ~= nil then
285 func = group[funcname]
287 handler = group.handler or handler
290 info.options = options
292 info.arg = option.arg
295 if type(func) == "string" then
296 if handler and handler[func] then
297 a,b,c,d = handler[func](handler, info, ...)
299 error(string.format("Method %s doesn't exist in handler for type func", func))
301 elseif type(func) == "function" then
302 a,b,c,d = func(info, ...)
309 --tables to hold orders and names for options being sorted, will be created with new()
310 --prevents needing to call functions repeatedly while sorting
314 local function compareOptions(a,b)
321 local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100
322 if OrderA == OrderB then
323 local NameA = (type(tempNames[a]) == "string") and tempNames[a] or ""
324 local NameB = (type(tempNames[b]) == "string") and tempNames[b] or ""
325 return NameA:upper() < NameB:upper()
336 return OrderA < OrderB
341 --builds 2 tables out of an options group
342 -- keySort, sorted keys
343 -- opts, combined options from .plugins and args
344 local function BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
348 if group.plugins then
349 for plugin, t in pairs(group.plugins) do
350 for k, v in pairs(t) do
356 tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
357 tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
364 for k, v in pairs(group.args) do
370 tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
371 tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
376 tsort(keySort, compareOptions)
382 local function DelTree(tree)
383 if tree.children then
384 local childs = tree.children
385 for i = 1, #childs do
393 local function CleanUserData(widget, event)
395 local user = widget:GetUserDataTable()
401 if widget.type == "TreeGroup" then
402 local tree = user.tree
413 if widget.type == "TabGroup" then
420 if widget.type == "DropdownGroup" then
421 widget:SetGroupList(nil)
422 if user.grouplist then
425 if user.orderlist then
431 -- - Gets a status table for the given appname and options path.
432 -- @param appName The application name as given to `:RegisterOptionsTable()`
433 -- @param path The path to the options (a table with all group keys)
435 function AceConfigDialog:GetStatusTable(appName, path)
436 local status = self.Status
438 if not status[appName] then
440 status[appName].status = {}
441 status[appName].children = {}
444 status = status[appName]
449 if not status.children[v] then
450 status.children[v] = {}
451 status.children[v].status = {}
452 status.children[v].children = {}
454 status = status.children[v]
461 --- Selects the specified path in the options window.
462 -- The path specified has to match the keys of the groups in the table.
463 -- @param appName The application name as given to `:RegisterOptionsTable()`
464 -- @param ... The path to the key that should be selected
465 function AceConfigDialog:SelectGroup(appName, ...)
469 local app = reg:GetOptionsTable(appName)
471 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
473 local options = app("dialog", MAJOR)
474 local group = options
475 local status = self:GetStatusTable(appName, path)
476 if not status.groups then
479 status = status.groups
483 for n = 1, select("#",...) do
484 local key = select(n, ...)
486 if group.childGroups == "tab" or group.childGroups == "select" then
487 --if this is a tab or select group, select the group
488 status.selected = key
489 --children of this group are no longer extra levels of a tree
492 --tree group by default
494 --this is an extra level of a tree group, build a uniquevalue for it
495 treevalue = treevalue.."\001"..key
497 --this is the top level of a tree group, the uniquevalue is the same as the key
499 if not status.groups then
502 --save this trees status table for any extra levels or groups
505 --make sure that the tree entry is open, and select it.
506 --the selected group will be overwritten if a child is the final target but still needs to be open
507 treestatus.selected = treevalue
508 treestatus.groups[treevalue] = true
512 --move to the next group in the path
513 group = GetSubOption(group, key)
518 status = self:GetStatusTable(appName, path)
519 if not status.groups then
522 status = status.groups
526 reg:NotifyChange(appName)
529 local function OptionOnMouseOver(widget, event)
530 --show a tooltip/set the status bar to the desc text
531 local user = widget:GetUserDataTable()
532 local opt = user.option
533 local options = user.options
534 local path = user.path
535 local appName = user.appName
537 GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT")
538 local name = GetOptionsMemberValue("name", opt, options, path, appName)
539 local desc = GetOptionsMemberValue("desc", opt, options, path, appName)
540 local usage = GetOptionsMemberValue("usage", opt, options, path, appName)
541 local descStyle = opt.descStyle
543 if descStyle and descStyle ~= "tooltip" then return end
545 GameTooltip:SetText(name, 1, .82, 0, 1)
547 if opt.type == "multiselect" then
548 GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1)
550 if type(desc) == "string" then
551 GameTooltip:AddLine(desc, 1, 1, 1, 1)
553 if type(usage) == "string" then
554 GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
560 local function OptionOnMouseLeave(widget, event)
564 local function GetFuncName(option)
565 local type = option.type
566 if type == "execute" then
572 local function confirmPopup(appName, rootframe, basepath, info, message, func, ...)
573 if not StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] then
574 StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] = {}
576 local t = StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"]
583 t.preferredIndex = STATICPOPUP_NUMDIALOGS
584 local dialog, oldstrata
585 t.OnAccept = function()
586 safecall(func, unpack(t))
587 if dialog and oldstrata then
588 dialog:SetFrameStrata(oldstrata)
590 AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
593 t.OnCancel = function()
594 if dialog and oldstrata then
595 dialog:SetFrameStrata(oldstrata)
597 AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
600 for i = 1, select("#", ...) do
601 t[i] = select(i, ...) or false
607 dialog = StaticPopup_Show("ACECONFIGDIALOG30_CONFIRM_DIALOG")
609 oldstrata = dialog:GetFrameStrata()
610 dialog:SetFrameStrata("TOOLTIP")
614 local function ActivateControl(widget, event, ...)
615 --This function will call the set / execute handler for the widget
616 --widget:GetUserDataTable() contains the needed info
617 local user = widget:GetUserDataTable()
618 local option = user.option
619 local options = user.options
620 local path = user.path
624 local group = options
625 local funcname = GetFuncName(option)
629 --build the info table containing the path
630 -- pick up functions while traversing the tree
631 if group[funcname] ~= nil then
632 func = group[funcname]
634 handler = group.handler or handler
635 confirm = group.confirm
636 validate = group.validate
639 group = GetSubOption(group, v)
641 if group[funcname] ~= nil then
642 func = group[funcname]
644 handler = group.handler or handler
645 if group.confirm ~= nil then
646 confirm = group.confirm
648 if group.validate ~= nil then
649 validate = group.validate
653 info.options = options
654 info.appName = user.appName
655 info.arg = option.arg
656 info.handler = handler
658 info.type = option.type
659 info.uiType = "dialog"
663 if type(option.name) == "function" then
664 name = option.name(info)
665 elseif type(option.name) == "string" then
670 local usage = option.usage
671 local pattern = option.pattern
673 local validated = true
675 if option.type == "input" then
676 if type(pattern)=="string" then
677 if not strmatch(..., pattern) then
684 if validated and option.type ~= "execute" then
685 if type(validate) == "string" then
686 if handler and handler[validate] then
687 success, validated = safecall(handler[validate], handler, info, ...)
688 if not success then validated = false end
690 error(format("Method %s doesn't exist in handler for type execute", validate))
692 elseif type(validate) == "function" then
693 success, validated = safecall(validate, info, ...)
694 if not success then validated = false end
698 local rootframe = user.rootframe
699 if type(validated) == "string" then
700 --validate function returned a message to display
701 if rootframe.SetStatusText then
702 rootframe:SetStatusText(validated)
704 -- TODO: do something else.
706 PlaySound("igPlayerInviteDecline")
709 elseif not validated then
710 --validate returned false
711 if rootframe.SetStatusText then
713 rootframe:SetStatusText(name..": "..usage)
716 rootframe:SetStatusText(name..": Expected "..pattern)
718 rootframe:SetStatusText(name..": Invalid Value")
722 -- TODO: do something else
724 PlaySound("igPlayerInviteDecline")
729 local confirmText = option.confirmText
730 --call confirm func/method
731 if type(confirm) == "string" then
732 if handler and handler[confirm] then
733 success, confirm = safecall(handler[confirm], handler, info, ...)
734 if success and type(confirm) == "string" then
735 confirmText = confirm
737 elseif not success then
741 error(format("Method %s doesn't exist in handler for type confirm", confirm))
743 elseif type(confirm) == "function" then
744 success, confirm = safecall(confirm, info, ...)
745 if success and type(confirm) == "string" then
746 confirmText = confirm
748 elseif not success then
754 if type(confirm) == "boolean" then
756 if not confirmText then
757 local name, desc = option.name, option.desc
758 if type(name) == "function" then
761 if type(desc) == "function" then
766 confirmText = confirmText.." - "..desc
770 local iscustom = user.rootframe:GetUserData("iscustom")
774 rootframe = user.rootframe
776 local basepath = user.rootframe:GetUserData("basepath")
777 if type(func) == "string" then
778 if handler and handler[func] then
779 confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...)
781 error(format("Method %s doesn't exist in handler for type func", func))
783 elseif type(func) == "function" then
784 confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...)
786 --func will be called and info deleted when the confirm dialog is responded to
792 if type(func) == "string" then
793 if handler and handler[func] then
794 safecall(handler[func],handler, info, ...)
796 error(format("Method %s doesn't exist in handler for type func", func))
798 elseif type(func) == "function" then
799 safecall(func,info, ...)
804 local iscustom = user.rootframe:GetUserData("iscustom")
805 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
806 --full refresh of the frame, some controls dont cause this on all events
807 if option.type == "color" then
808 if event == "OnValueConfirmed" then
811 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
813 AceConfigDialog:Open(user.appName, unpack(basepath))
816 elseif option.type == "range" then
817 if event == "OnMouseUp" then
819 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
821 AceConfigDialog:Open(user.appName, unpack(basepath))
824 --multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed'
825 elseif option.type == "multiselect" then
826 user.valuechanged = true
829 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
831 AceConfigDialog:Open(user.appName, unpack(basepath))
839 local function ActivateSlider(widget, event, value)
840 local option = widget:GetUserData("option")
841 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
844 value = math_floor((value - min) / step + 0.5) * step + min
846 value = math_max(value, min)
849 value = math_min(value, max)
851 ActivateControl(widget,event,value)
854 --called from a checkbox that is part of an internally created multiselect group
855 --this type is safe to refresh on activation of one control
856 local function ActivateMultiControl(widget, event, ...)
857 ActivateControl(widget, event, widget:GetUserData("value"), ...)
858 local user = widget:GetUserDataTable()
859 local iscustom = user.rootframe:GetUserData("iscustom")
860 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
862 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
864 AceConfigDialog:Open(user.appName, unpack(basepath))
868 local function MultiControlOnClosed(widget, event, ...)
869 local user = widget:GetUserDataTable()
870 if user.valuechanged then
871 local iscustom = user.rootframe:GetUserData("iscustom")
872 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
874 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
876 AceConfigDialog:Open(user.appName, unpack(basepath))
881 local function FrameOnClose(widget, event)
882 local appName = widget:GetUserData("appName")
883 AceConfigDialog.OpenFrames[appName] = nil
887 local function CheckOptionHidden(option, options, path, appName)
888 --check for a specific boolean option
889 local hidden = pickfirstset(option.dialogHidden,option.guiHidden)
890 if hidden ~= nil then
894 return GetOptionsMemberValue("hidden", option, options, path, appName)
897 local function CheckOptionDisabled(option, options, path, appName)
898 --check for a specific boolean option
899 local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled)
900 if disabled ~= nil then
904 return GetOptionsMemberValue("disabled", option, options, path, appName)
907 local function BuildTabs(group, options, path, appName)
910 local keySort = new()
913 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
915 for i = 1, #keySort do
918 if v.type == "group" then
920 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
921 local hidden = CheckOptionHidden(v, options, path, appName)
922 if not inline and not hidden then
924 text[k] = GetOptionsMemberValue("name", v, options, path, appName)
936 local function BuildSelect(group, options, path, appName)
939 local keySort = new()
942 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
944 for i = 1, #keySort do
947 if v.type == "group" then
949 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
950 local hidden = CheckOptionHidden(v, options, path, appName)
951 if not inline and not hidden then
952 groups[k] = GetOptionsMemberValue("name", v, options, path, appName)
965 local function BuildSubGroups(group, tree, options, path, appName)
966 local keySort = new()
969 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
971 for i = 1, #keySort do
974 if v.type == "group" then
976 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
977 local hidden = CheckOptionHidden(v, options, path, appName)
978 if not inline and not hidden then
981 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
982 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
983 entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName)
984 entry.disabled = CheckOptionDisabled(v, options, path, appName)
985 if not tree.children then tree.children = new() end
986 tinsert(tree.children,entry)
987 if (v.childGroups or "tree") == "tree" then
988 BuildSubGroups(v,entry, options, path, appName)
999 local function BuildGroups(group, options, path, appName, recurse)
1001 local keySort = new()
1004 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1006 for i = 1, #keySort do
1007 local k = keySort[i]
1009 if v.type == "group" then
1011 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1012 local hidden = CheckOptionHidden(v, options, path, appName)
1013 if not inline and not hidden then
1016 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
1017 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
1018 entry.disabled = CheckOptionDisabled(v, options, path, appName)
1020 if recurse and (v.childGroups or "tree") == "tree" then
1021 BuildSubGroups(v,entry, options, path, appName)
1032 local function InjectInfo(control, options, option, path, rootframe, appName)
1033 local user = control:GetUserDataTable()
1037 user.rootframe = rootframe
1038 user.option = option
1039 user.options = options
1040 user.path = copy(path)
1041 user.appName = appName
1042 control:SetCallback("OnRelease", CleanUserData)
1043 control:SetCallback("OnLeave", OptionOnMouseLeave)
1044 control:SetCallback("OnEnter", OptionOnMouseOver)
1049 options - root of the options table being fed
1050 container - widget that controls will be placed in
1051 rootframe - Frame object the options are in
1052 path - table with the keys to get to the group being fed
1055 local function FeedOptions(appName, options,container,rootframe,path,group,inline)
1056 local keySort = new()
1059 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1061 for i = 1, #keySort do
1062 local k = keySort[i]
1065 local hidden = CheckOptionHidden(v, options, path, appName)
1066 local name = GetOptionsMemberValue("name", v, options, path, appName)
1068 if v.type == "group" then
1069 if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
1071 local GroupContainer
1072 if name and name ~= "" then
1073 GroupContainer = gui:Create("InlineGroup")
1074 GroupContainer:SetTitle(name or "")
1076 GroupContainer = gui:Create("SimpleGroup")
1079 GroupContainer.width = "fill"
1080 GroupContainer:SetLayout("flow")
1081 container:AddChild(GroupContainer)
1082 FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
1088 local name = GetOptionsMemberValue("name", v, options, path, appName)
1090 if v.type == "execute" then
1092 local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1093 local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1095 if type(image) == "string" then
1096 control = gui:Create("Icon")
1098 width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1101 height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1103 if type(imageCoords) == "table" then
1104 control:SetImage(image, unpack(imageCoords))
1106 control:SetImage(image)
1108 if type(width) ~= "number" then
1111 if type(height) ~= "number" then
1114 control:SetImageSize(width, height)
1115 control:SetLabel(name)
1117 control = gui:Create("Button")
1118 control:SetText(name)
1120 control:SetCallback("OnClick",ActivateControl)
1122 elseif v.type == "input" then
1123 local controlType = v.dialogControl or v.control or (v.multiline and "MultiLineEditBox") or "EditBox"
1124 control = gui:Create(controlType)
1126 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1127 control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox")
1130 if v.multiline and control.SetNumLines then
1131 control:SetNumLines(tonumber(v.multiline) or 4)
1133 control:SetLabel(name)
1134 control:SetCallback("OnEnterPressed",ActivateControl)
1135 local text = GetOptionsMemberValue("get",v, options, path, appName)
1136 if type(text) ~= "string" then
1139 control:SetText(text)
1141 elseif v.type == "toggle" then
1142 control = gui:Create("CheckBox")
1143 control:SetLabel(name)
1144 control:SetTriState(v.tristate)
1145 local value = GetOptionsMemberValue("get",v, options, path, appName)
1146 control:SetValue(value)
1147 control:SetCallback("OnValueChanged",ActivateControl)
1149 if v.descStyle == "inline" then
1150 local desc = GetOptionsMemberValue("desc", v, options, path, appName)
1151 control:SetDescription(desc)
1154 local image = GetOptionsMemberValue("image", v, options, path, appName)
1155 local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName)
1157 if type(image) == "string" then
1158 if type(imageCoords) == "table" then
1159 control:SetImage(image, unpack(imageCoords))
1161 control:SetImage(image)
1164 elseif v.type == "range" then
1165 control = gui:Create("Slider")
1166 control:SetLabel(name)
1167 control:SetSliderValues(v.softMin or v.min or 0, v.softMax or v.max or 100, v.bigStep or v.step or 0)
1168 control:SetIsPercent(v.isPercent)
1169 local value = GetOptionsMemberValue("get",v, options, path, appName)
1170 if type(value) ~= "number" then
1173 control:SetValue(value)
1174 control:SetCallback("OnValueChanged",ActivateSlider)
1175 control:SetCallback("OnMouseUp",ActivateSlider)
1177 elseif v.type == "select" then
1178 local values = GetOptionsMemberValue("values", v, options, path, appName)
1179 if v.style == "radio" then
1180 local disabled = CheckOptionDisabled(v, options, path, appName)
1181 local width = GetOptionsMemberValue("width",v,options,path,appName)
1182 control = gui:Create("InlineGroup")
1183 control:SetLayout("Flow")
1184 control:SetTitle(name)
1185 control.width = "fill"
1187 control:PauseLayout()
1188 local optionValue = GetOptionsMemberValue("get",v, options, path, appName)
1190 for value, text in pairs(values) do
1194 for k, value in ipairs(t) do
1195 local text = values[value]
1196 local radio = gui:Create("CheckBox")
1197 radio:SetLabel(text)
1198 radio:SetUserData("value", value)
1199 radio:SetUserData("text", text)
1200 radio:SetDisabled(disabled)
1201 radio:SetType("radio")
1202 radio:SetValue(optionValue == value)
1203 radio:SetCallback("OnValueChanged", ActivateMultiControl)
1204 InjectInfo(radio, options, v, path, rootframe, appName)
1205 control:AddChild(radio)
1206 if width == "double" then
1207 radio:SetWidth(width_multiplier * 2)
1208 elseif width == "half" then
1209 radio:SetWidth(width_multiplier / 2)
1210 elseif width == "full" then
1211 radio.width = "fill"
1213 radio:SetWidth(width_multiplier)
1216 control:ResumeLayout()
1219 local controlType = v.dialogControl or v.control or "Dropdown"
1220 control = gui:Create(controlType)
1222 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1223 control = gui:Create("Dropdown")
1225 local itemType = v.itemControl
1226 if itemType and not gui:GetWidgetVersion(itemType) then
1227 geterrorhandler()(("Invalid Custom Item Type - %s"):format(tostring(itemType)))
1230 control:SetLabel(name)
1231 control:SetList(values, nil, itemType)
1232 local value = GetOptionsMemberValue("get",v, options, path, appName)
1233 if not values[value] then
1236 control:SetValue(value)
1237 control:SetCallback("OnValueChanged", ActivateControl)
1240 elseif v.type == "multiselect" then
1241 local values = GetOptionsMemberValue("values", v, options, path, appName)
1242 local disabled = CheckOptionDisabled(v, options, path, appName)
1244 local controlType = v.dialogControl or v.control
1246 local valuesort = new()
1248 for value, text in pairs(values) do
1249 tinsert(valuesort, value)
1255 control = gui:Create(controlType)
1257 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1261 control:SetMultiselect(true)
1262 control:SetLabel(name)
1263 control:SetList(values)
1264 control:SetDisabled(disabled)
1265 control:SetCallback("OnValueChanged",ActivateControl)
1266 control:SetCallback("OnClosed", MultiControlOnClosed)
1267 local width = GetOptionsMemberValue("width",v,options,path,appName)
1268 if width == "double" then
1269 control:SetWidth(width_multiplier * 2)
1270 elseif width == "half" then
1271 control:SetWidth(width_multiplier / 2)
1272 elseif width == "full" then
1273 control.width = "fill"
1275 control:SetWidth(width_multiplier)
1277 --check:SetTriState(v.tristate)
1278 for i = 1, #valuesort do
1279 local key = valuesort[i]
1280 local value = GetOptionsMemberValue("get",v, options, path, appName, key)
1281 control:SetItemValue(key,value)
1284 control = gui:Create("InlineGroup")
1285 control:SetLayout("Flow")
1286 control:SetTitle(name)
1287 control.width = "fill"
1289 control:PauseLayout()
1290 local width = GetOptionsMemberValue("width",v,options,path,appName)
1291 for i = 1, #valuesort do
1292 local value = valuesort[i]
1293 local text = values[value]
1294 local check = gui:Create("CheckBox")
1295 check:SetLabel(text)
1296 check:SetUserData("value", value)
1297 check:SetUserData("text", text)
1298 check:SetDisabled(disabled)
1299 check:SetTriState(v.tristate)
1300 check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
1301 check:SetCallback("OnValueChanged",ActivateMultiControl)
1302 InjectInfo(check, options, v, path, rootframe, appName)
1303 control:AddChild(check)
1304 if width == "double" then
1305 check:SetWidth(width_multiplier * 2)
1306 elseif width == "half" then
1307 check:SetWidth(width_multiplier / 2)
1308 elseif width == "full" then
1309 check.width = "fill"
1311 check:SetWidth(width_multiplier)
1314 control:ResumeLayout()
1322 elseif v.type == "color" then
1323 control = gui:Create("ColorPicker")
1324 control:SetLabel(name)
1325 control:SetHasAlpha(GetOptionsMemberValue("hasAlpha",v, options, path, appName))
1326 control:SetColor(GetOptionsMemberValue("get",v, options, path, appName))
1327 control:SetCallback("OnValueChanged",ActivateControl)
1328 control:SetCallback("OnValueConfirmed",ActivateControl)
1330 elseif v.type == "keybinding" then
1331 control = gui:Create("Keybinding")
1332 control:SetLabel(name)
1333 control:SetKey(GetOptionsMemberValue("get",v, options, path, appName))
1334 control:SetCallback("OnKeyChanged",ActivateControl)
1336 elseif v.type == "header" then
1337 control = gui:Create("Heading")
1338 control:SetText(name)
1339 control.width = "fill"
1341 elseif v.type == "description" then
1342 control = gui:Create("Label")
1343 control:SetText(name)
1345 local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName)
1346 if fontSize == "medium" then
1347 control:SetFontObject(GameFontHighlight)
1348 elseif fontSize == "large" then
1349 control:SetFontObject(GameFontHighlightLarge)
1350 else -- small or invalid
1351 control:SetFontObject(GameFontHighlightSmall)
1354 local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1355 local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1357 if type(image) == "string" then
1359 width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1362 height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1364 if type(imageCoords) == "table" then
1365 control:SetImage(image, unpack(imageCoords))
1367 control:SetImage(image)
1369 if type(width) ~= "number" then
1372 if type(height) ~= "number" then
1375 control:SetImageSize(width, height)
1377 local width = GetOptionsMemberValue("width",v,options,path,appName)
1378 control.width = not width and "fill"
1383 if control.width ~= "fill" then
1384 local width = GetOptionsMemberValue("width",v,options,path,appName)
1385 if width == "double" then
1386 control:SetWidth(width_multiplier * 2)
1387 elseif width == "half" then
1388 control:SetWidth(width_multiplier / 2)
1389 elseif width == "full" then
1390 control.width = "fill"
1392 control:SetWidth(width_multiplier)
1395 if control.SetDisabled then
1396 local disabled = CheckOptionDisabled(v, options, path, appName)
1397 control:SetDisabled(disabled)
1400 InjectInfo(control, options, v, path, rootframe, appName)
1401 container:AddChild(control)
1408 container:ResumeLayout()
1409 container:DoLayout()
1414 local function BuildPath(path, ...)
1415 for i = 1, select("#",...) do
1416 tinsert(path, (select(i,...)))
1421 local function TreeOnButtonEnter(widget, event, uniquevalue, button)
1422 local user = widget:GetUserDataTable()
1423 if not user then return end
1424 local options = user.options
1425 local option = user.option
1426 local path = user.path
1427 local appName = user.appName
1429 local feedpath = new()
1431 feedpath[i] = path[i]
1434 BuildPath(feedpath, ("\001"):split(uniquevalue))
1435 local group = options
1436 for i = 1, #feedpath do
1437 if not group then return end
1438 group = GetSubOption(group, feedpath[i])
1441 local name = GetOptionsMemberValue("name", group, options, feedpath, appName)
1442 local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName)
1444 GameTooltip:SetOwner(button, "ANCHOR_NONE")
1445 if widget.type == "TabGroup" then
1446 GameTooltip:SetPoint("BOTTOM",button,"TOP")
1448 GameTooltip:SetPoint("LEFT",button,"RIGHT")
1451 GameTooltip:SetText(name, 1, .82, 0, 1)
1453 if type(desc) == "string" then
1454 GameTooltip:AddLine(desc, 1, 1, 1, 1)
1460 local function TreeOnButtonLeave(widget, event, value, button)
1465 local function GroupExists(appName, options, path, uniquevalue)
1466 if not uniquevalue then return false end
1468 local feedpath = new()
1469 local temppath = new()
1471 feedpath[i] = path[i]
1474 BuildPath(feedpath, ("\001"):split(uniquevalue))
1476 local group = options
1477 for i = 1, #feedpath do
1478 local v = feedpath[i]
1480 group = GetSubOption(group, v)
1482 if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then
1493 local function GroupSelected(widget, event, uniquevalue)
1495 local user = widget:GetUserDataTable()
1497 local options = user.options
1498 local option = user.option
1499 local path = user.path
1500 local rootframe = user.rootframe
1502 local feedpath = new()
1504 feedpath[i] = path[i]
1507 BuildPath(feedpath, ("\001"):split(uniquevalue))
1508 local group = options
1509 for i = 1, #feedpath do
1510 group = GetSubOption(group, feedpath[i])
1512 widget:ReleaseChildren()
1513 AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath)
1522 This function will feed one group, and any inline child groups into the given container
1523 Select Groups will only have the selection control (tree, tabs, dropdown) fed in
1524 and have a group selected, this event will trigger the feeding of child groups
1527 If the group is Inline, FeedOptions
1528 If the group has no child groups, FeedOptions
1530 If the group is a tab or select group, FeedOptions then add the Group Control
1531 If the group is a tree group FeedOptions then
1532 its parent isnt a tree group: then add the tree control containing this and all child tree groups
1533 if its parent is a tree group, its already a node on a tree
1536 function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot)
1537 local group = options
1538 --follow the path to get to the curent group
1540 local grouptype, parenttype = options.childGroups, "none"
1545 group = GetSubOption(group, v)
1546 inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1547 parenttype = grouptype
1548 grouptype = group.childGroups
1551 if not parenttype then
1555 --check if the group has child groups
1556 local hasChildGroups
1557 for k, v in pairs(group.args) do
1558 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1559 hasChildGroups = true
1562 if group.plugins then
1563 for plugin, t in pairs(group.plugins) do
1564 for k, v in pairs(t) do
1565 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1566 hasChildGroups = true
1572 container:SetLayout("flow")
1575 --Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on
1576 if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and (parenttype == "tree" and not isRoot)) then
1577 if container.type ~= "InlineGroup" and container.type ~= "SimpleGroup" then
1578 scroll = gui:Create("ScrollFrame")
1579 scroll:SetLayout("flow")
1580 scroll.width = "fill"
1581 scroll.height = "fill"
1582 container:SetLayout("fill")
1583 container:AddChild(scroll)
1588 FeedOptions(appName,options,container,rootframe,path,group,nil)
1591 container:PerformLayout()
1592 local status = self:GetStatusTable(appName, path)
1593 if not status.scroll then
1596 scroll:SetStatusTable(status.scroll)
1599 if hasChildGroups and not inline then
1600 local name = GetOptionsMemberValue("name", group, options, path, appName)
1601 if grouptype == "tab" then
1603 local tab = gui:Create("TabGroup")
1604 InjectInfo(tab, options, group, path, rootframe, appName)
1605 tab:SetCallback("OnGroupSelected", GroupSelected)
1606 tab:SetCallback("OnTabEnter", TreeOnButtonEnter)
1607 tab:SetCallback("OnTabLeave", TreeOnButtonLeave)
1609 local status = AceConfigDialog:GetStatusTable(appName, path)
1610 if not status.groups then
1613 tab:SetStatusTable(status.groups)
1617 local tabs = BuildGroups(group, options, path, appName)
1619 tab:SetUserData("tablist", tabs)
1622 local entry = tabs[i]
1623 if not entry.disabled then
1624 tab:SelectTab((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1629 container:AddChild(tab)
1631 elseif grouptype == "select" then
1633 local select = gui:Create("DropdownGroup")
1634 select:SetTitle(name)
1635 InjectInfo(select, options, group, path, rootframe, appName)
1636 select:SetCallback("OnGroupSelected", GroupSelected)
1637 local status = AceConfigDialog:GetStatusTable(appName, path)
1638 if not status.groups then
1641 select:SetStatusTable(status.groups)
1642 local grouplist, orderlist = BuildSelect(group, options, path, appName)
1643 select:SetGroupList(grouplist, orderlist)
1644 select:SetUserData("grouplist", grouplist)
1645 select:SetUserData("orderlist", orderlist)
1647 local firstgroup = orderlist[1]
1649 select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup)
1652 select.width = "fill"
1653 select.height = "fill"
1655 container:AddChild(select)
1657 --assume tree group by default
1658 --if parenttype is tree then this group is already a node on that tree
1659 elseif (parenttype ~= "tree") or isRoot then
1660 local tree = gui:Create("TreeGroup")
1661 InjectInfo(tree, options, group, path, rootframe, appName)
1662 tree:EnableButtonTooltips(false)
1665 tree.height = "fill"
1667 tree:SetCallback("OnGroupSelected", GroupSelected)
1668 tree:SetCallback("OnButtonEnter", TreeOnButtonEnter)
1669 tree:SetCallback("OnButtonLeave", TreeOnButtonLeave)
1671 local status = AceConfigDialog:GetStatusTable(appName, path)
1672 if not status.groups then
1675 local treedefinition = BuildGroups(group, options, path, appName, true)
1676 tree:SetStatusTable(status.groups)
1678 tree:SetTree(treedefinition)
1679 tree:SetUserData("tree",treedefinition)
1681 for i = 1, #treedefinition do
1682 local entry = treedefinition[i]
1683 if not entry.disabled then
1684 tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1689 container:AddChild(tree)
1694 local old_CloseSpecialWindows
1697 local function RefreshOnUpdate(this)
1698 for appName in pairs(this.closing) do
1699 if AceConfigDialog.OpenFrames[appName] then
1700 AceConfigDialog.OpenFrames[appName]:Hide()
1702 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1703 for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1704 if not widget:IsVisible() then
1705 widget:ReleaseChildren()
1709 this.closing[appName] = nil
1712 if this.closeAll then
1713 for k, v in pairs(AceConfigDialog.OpenFrames) do
1714 if not this.closeAllOverride[k] then
1719 wipe(this.closeAllOverride)
1722 for appName in pairs(this.apps) do
1723 if AceConfigDialog.OpenFrames[appName] then
1724 local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable()
1725 AceConfigDialog:Open(appName, unpack(user.basepath or emptyTbl))
1727 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1728 for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1729 local user = widget:GetUserDataTable()
1730 if widget:IsVisible() then
1731 AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(user.basepath or emptyTbl))
1735 this.apps[appName] = nil
1737 this:SetScript("OnUpdate", nil)
1740 -- Upgrade the OnUpdate script as well, if needed.
1741 if AceConfigDialog.frame:GetScript("OnUpdate") then
1742 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1745 --- Close all open options windows
1746 function AceConfigDialog:CloseAll()
1747 AceConfigDialog.frame.closeAll = true
1748 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1749 if next(self.OpenFrames) then
1754 --- Close a specific options window.
1755 -- @param appName The application name as given to `:RegisterOptionsTable()`
1756 function AceConfigDialog:Close(appName)
1757 if self.OpenFrames[appName] then
1758 AceConfigDialog.frame.closing[appName] = true
1759 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1764 -- Internal -- Called by AceConfigRegistry
1765 function AceConfigDialog:ConfigTableChanged(event, appName)
1766 AceConfigDialog.frame.apps[appName] = true
1767 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1770 reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged")
1772 --- Sets the default size of the options window for a specific application.
1773 -- @param appName The application name as given to `:RegisterOptionsTable()`
1774 -- @param width The default width
1775 -- @param height The default height
1776 function AceConfigDialog:SetDefaultSize(appName, width, height)
1777 local status = AceConfigDialog:GetStatusTable(appName)
1778 if type(width) == "number" and type(height) == "number" then
1779 status.width = width
1780 status.height = height
1784 --- Open an option window at the specified path (if any).
1785 -- This function can optionally feed the group into a pre-created container
1786 -- instead of creating a new container frame.
1787 -- @paramsig appName [, container][, ...]
1788 -- @param appName The application name as given to `:RegisterOptionsTable()`
1789 -- @param container An optional container frame to feed the options into
1790 -- @param ... The path to open after creating the options window (see `:SelectGroup` for details)
1791 function AceConfigDialog:Open(appName, container, ...)
1792 if not old_CloseSpecialWindows then
1793 old_CloseSpecialWindows = CloseSpecialWindows
1794 CloseSpecialWindows = function()
1795 local found = old_CloseSpecialWindows()
1796 return self:CloseAll() or found
1799 local app = reg:GetOptionsTable(appName)
1801 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
1803 local options = app("dialog", MAJOR)
1808 local name = GetOptionsMemberValue("name", options, options, path, appName)
1810 --If an optional path is specified add it to the path table before feeding the options
1811 --as container is optional as well it may contain the first element of the path
1812 if type(container) == "string" then
1813 tinsert(path, container)
1816 for n = 1, select("#",...) do
1817 tinsert(path, (select(n, ...)))
1820 --if a container is given feed into that
1824 f:SetUserData("appName", appName)
1825 f:SetUserData("iscustom", true)
1827 f:SetUserData("basepath", copy(path))
1829 local status = AceConfigDialog:GetStatusTable(appName)
1830 if not status.width then
1833 if not status.height then
1836 if f.SetStatusTable then
1837 f:SetStatusTable(status)
1840 f:SetTitle(name or "")
1843 if not self.OpenFrames[appName] then
1844 f = gui:Create("Frame")
1845 self.OpenFrames[appName] = f
1847 f = self.OpenFrames[appName]
1850 f:SetCallback("OnClose", FrameOnClose)
1851 f:SetUserData("appName", appName)
1853 f:SetUserData("basepath", copy(path))
1855 f:SetTitle(name or "")
1856 local status = AceConfigDialog:GetStatusTable(appName)
1857 f:SetStatusTable(status)
1860 self:FeedGroup(appName,options,f,f,path,true)
1866 if AceConfigDialog.frame.closeAll then
1867 -- close all is set, but thats not good, since we're just opening here, so force it
1868 AceConfigDialog.frame.closeAllOverride[appName] = true
1872 -- convert pre-39 BlizOptions structure to the new format
1873 if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then
1874 local old = AceConfigDialog.BlizOptions
1876 for key, widget in pairs(old) do
1877 local appName = widget:GetUserData("appName")
1878 if not new[appName] then new[appName] = {} end
1879 new[appName][key] = widget
1881 AceConfigDialog.BlizOptions = new
1883 AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {}
1886 local function FeedToBlizPanel(widget, event)
1887 local path = widget:GetUserData("path")
1888 AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl))
1891 local function ClearBlizPanel(widget, event)
1892 local appName = widget:GetUserData("appName")
1893 AceConfigDialog.frame.closing[appName] = true
1894 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1897 --- Add an option table into the Blizzard Interface Options panel.
1898 -- You can optionally supply a descriptive name to use and a parent frame to use,
1899 -- as well as a path in the options table.\\
1900 -- If no name is specified, the appName will be used instead.
1902 -- If you specify a proper `parent` (by name), the interface options will generate a
1903 -- tree layout. Note that only one level of children is supported, so the parent always
1904 -- has to be a head-level note.
1906 -- This function returns a reference to the container frame registered with the Interface
1907 -- Options. You can use this reference to open the options with the API function
1908 -- `InterfaceOptionsFrame_OpenToCategory`.
1909 -- @param appName The application name as given to `:RegisterOptionsTable()`
1910 -- @param name A descriptive name to display in the options tree (defaults to appName)
1911 -- @param parent The parent to use in the interface options tree.
1912 -- @param ... The path in the options table to feed into the interface options panel.
1913 -- @return The reference to the frame registered into the Interface Options.
1914 function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
1915 local BlizOptions = AceConfigDialog.BlizOptions
1918 for n = 1, select("#", ...) do
1919 key = key.."\001"..select(n, ...)
1922 if not BlizOptions[appName] then
1923 BlizOptions[appName] = {}
1926 if not BlizOptions[appName][key] then
1927 local group = gui:Create("BlizOptionsGroup")
1928 BlizOptions[appName][key] = group
1929 group:SetName(name or appName, parent)
1931 group:SetTitle(name or appName)
1932 group:SetUserData("appName", appName)
1933 if select("#", ...) > 0 then
1935 for n = 1, select("#",...) do
1936 tinsert(path, (select(n, ...)))
1938 group:SetUserData("path", path)
1940 group:SetCallback("OnShow", FeedToBlizPanel)
1941 group:SetCallback("OnHide", ClearBlizPanel)
1942 InterfaceOptions_AddCategory(group.frame)
1945 error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2)