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 1163 2017-08-14 14:04:39Z nevcairiel $
6 local LibStub = LibStub
7 local gui = LibStub("AceGUI-3.0")
8 local reg = LibStub("AceConfigRegistry-3.0")
10 local MAJOR, MINOR = "AceConfigDialog-3.0", 64
11 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
13 if not AceConfigDialog then return end
15 AceConfigDialog.OpenFrames = AceConfigDialog.OpenFrames or {}
16 AceConfigDialog.Status = AceConfigDialog.Status or {}
17 AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame")
19 AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {}
20 AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
21 AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {}
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, true)
547 if opt.type == "multiselect" then
548 GameTooltip:AddLine(user.text, 0.5, 0.5, 0.8, true)
550 if type(desc) == "string" then
551 GameTooltip:AddLine(desc, 1, 1, 1, true)
553 if type(usage) == "string" then
554 GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true)
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 validationErrorPopup(message)
615 if not StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] then
616 StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] = {}
618 local t = StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"]
621 t.preferredIndex = STATICPOPUP_NUMDIALOGS
622 local dialog, oldstrata
623 t.OnAccept = function()
624 if dialog and oldstrata then
625 dialog:SetFrameStrata(oldstrata)
632 dialog = StaticPopup_Show("ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG")
634 oldstrata = dialog:GetFrameStrata()
635 dialog:SetFrameStrata("TOOLTIP")
639 local function ActivateControl(widget, event, ...)
640 --This function will call the set / execute handler for the widget
641 --widget:GetUserDataTable() contains the needed info
642 local user = widget:GetUserDataTable()
643 local option = user.option
644 local options = user.options
645 local path = user.path
649 local group = options
650 local funcname = GetFuncName(option)
654 --build the info table containing the path
655 -- pick up functions while traversing the tree
656 if group[funcname] ~= nil then
657 func = group[funcname]
659 handler = group.handler or handler
660 confirm = group.confirm
661 validate = group.validate
664 group = GetSubOption(group, v)
666 if group[funcname] ~= nil then
667 func = group[funcname]
669 handler = group.handler or handler
670 if group.confirm ~= nil then
671 confirm = group.confirm
673 if group.validate ~= nil then
674 validate = group.validate
678 info.options = options
679 info.appName = user.appName
680 info.arg = option.arg
681 info.handler = handler
683 info.type = option.type
684 info.uiType = "dialog"
688 if type(option.name) == "function" then
689 name = option.name(info)
690 elseif type(option.name) == "string" then
695 local usage = option.usage
696 local pattern = option.pattern
698 local validated = true
700 if option.type == "input" then
701 if type(pattern)=="string" then
702 if not strmatch(..., pattern) then
709 if validated and option.type ~= "execute" then
710 if type(validate) == "string" then
711 if handler and handler[validate] then
712 success, validated = safecall(handler[validate], handler, info, ...)
713 if not success then validated = false end
715 error(format("Method %s doesn't exist in handler for type execute", validate))
717 elseif type(validate) == "function" then
718 success, validated = safecall(validate, info, ...)
719 if not success then validated = false end
723 local rootframe = user.rootframe
724 if not validated or type(validated) == "string" then
725 if not validated then
727 validated = name..": "..usage
730 validated = name..": Expected "..pattern
732 validated = name..": Invalid Value"
737 -- show validate message
738 if rootframe.SetStatusText then
739 rootframe:SetStatusText(validated)
741 validationErrorPopup(validated)
743 PlaySound(PlaySoundKitID and "igPlayerInviteDecline" or 882) -- SOUNDKIT.IG_PLAYER_INVITE_DECLINE || XXX _DECLINE is actually missing from the table
748 local confirmText = option.confirmText
749 --call confirm func/method
750 if type(confirm) == "string" then
751 if handler and handler[confirm] then
752 success, confirm = safecall(handler[confirm], handler, info, ...)
753 if success and type(confirm) == "string" then
754 confirmText = confirm
756 elseif not success then
760 error(format("Method %s doesn't exist in handler for type confirm", confirm))
762 elseif type(confirm) == "function" then
763 success, confirm = safecall(confirm, info, ...)
764 if success and type(confirm) == "string" then
765 confirmText = confirm
767 elseif not success then
773 if type(confirm) == "boolean" then
775 if not confirmText then
776 local name, desc = option.name, option.desc
777 if type(name) == "function" then
780 if type(desc) == "function" then
785 confirmText = confirmText.." - "..desc
789 local iscustom = user.rootframe:GetUserData("iscustom")
793 rootframe = user.rootframe
795 local basepath = user.rootframe:GetUserData("basepath")
796 if type(func) == "string" then
797 if handler and handler[func] then
798 confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...)
800 error(format("Method %s doesn't exist in handler for type func", func))
802 elseif type(func) == "function" then
803 confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...)
805 --func will be called and info deleted when the confirm dialog is responded to
811 if type(func) == "string" then
812 if handler and handler[func] then
813 safecall(handler[func],handler, info, ...)
815 error(format("Method %s doesn't exist in handler for type func", func))
817 elseif type(func) == "function" then
818 safecall(func,info, ...)
823 local iscustom = user.rootframe:GetUserData("iscustom")
824 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
825 --full refresh of the frame, some controls dont cause this on all events
826 if option.type == "color" then
827 if event == "OnValueConfirmed" then
830 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
832 AceConfigDialog:Open(user.appName, unpack(basepath))
835 elseif option.type == "range" then
836 if event == "OnMouseUp" then
838 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
840 AceConfigDialog:Open(user.appName, unpack(basepath))
843 --multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed'
844 elseif option.type == "multiselect" then
845 user.valuechanged = true
848 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
850 AceConfigDialog:Open(user.appName, unpack(basepath))
858 local function ActivateSlider(widget, event, value)
859 local option = widget:GetUserData("option")
860 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
863 value = math_floor((value - min) / step + 0.5) * step + min
865 value = math_max(value, min)
868 value = math_min(value, max)
870 ActivateControl(widget,event,value)
873 --called from a checkbox that is part of an internally created multiselect group
874 --this type is safe to refresh on activation of one control
875 local function ActivateMultiControl(widget, event, ...)
876 ActivateControl(widget, event, widget:GetUserData("value"), ...)
877 local user = widget:GetUserDataTable()
878 local iscustom = user.rootframe:GetUserData("iscustom")
879 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
881 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
883 AceConfigDialog:Open(user.appName, unpack(basepath))
887 local function MultiControlOnClosed(widget, event, ...)
888 local user = widget:GetUserDataTable()
889 if user.valuechanged then
890 local iscustom = user.rootframe:GetUserData("iscustom")
891 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
893 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
895 AceConfigDialog:Open(user.appName, unpack(basepath))
900 local function FrameOnClose(widget, event)
901 local appName = widget:GetUserData("appName")
902 AceConfigDialog.OpenFrames[appName] = nil
906 local function CheckOptionHidden(option, options, path, appName)
907 --check for a specific boolean option
908 local hidden = pickfirstset(option.dialogHidden,option.guiHidden)
909 if hidden ~= nil then
913 return GetOptionsMemberValue("hidden", option, options, path, appName)
916 local function CheckOptionDisabled(option, options, path, appName)
917 --check for a specific boolean option
918 local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled)
919 if disabled ~= nil then
923 return GetOptionsMemberValue("disabled", option, options, path, appName)
926 local function BuildTabs(group, options, path, appName)
929 local keySort = new()
932 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
934 for i = 1, #keySort do
937 if v.type == "group" then
939 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
940 local hidden = CheckOptionHidden(v, options, path, appName)
941 if not inline and not hidden then
943 text[k] = GetOptionsMemberValue("name", v, options, path, appName)
955 local function BuildSelect(group, options, path, appName)
958 local keySort = new()
961 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
963 for i = 1, #keySort do
966 if v.type == "group" then
968 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
969 local hidden = CheckOptionHidden(v, options, path, appName)
970 if not inline and not hidden then
971 groups[k] = GetOptionsMemberValue("name", v, options, path, appName)
984 local function BuildSubGroups(group, tree, options, path, appName)
985 local keySort = new()
988 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
990 for i = 1, #keySort do
993 if v.type == "group" then
995 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
996 local hidden = CheckOptionHidden(v, options, path, appName)
997 if not inline and not hidden then
1000 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
1001 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
1002 entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName)
1003 entry.disabled = CheckOptionDisabled(v, options, path, appName)
1004 if not tree.children then tree.children = new() end
1005 tinsert(tree.children,entry)
1006 if (v.childGroups or "tree") == "tree" then
1007 BuildSubGroups(v,entry, options, path, appName)
1018 local function BuildGroups(group, options, path, appName, recurse)
1020 local keySort = new()
1023 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1025 for i = 1, #keySort do
1026 local k = keySort[i]
1028 if v.type == "group" then
1030 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1031 local hidden = CheckOptionHidden(v, options, path, appName)
1032 if not inline and not hidden then
1035 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
1036 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
1037 entry.disabled = CheckOptionDisabled(v, options, path, appName)
1039 if recurse and (v.childGroups or "tree") == "tree" then
1040 BuildSubGroups(v,entry, options, path, appName)
1051 local function InjectInfo(control, options, option, path, rootframe, appName)
1052 local user = control:GetUserDataTable()
1056 user.rootframe = rootframe
1057 user.option = option
1058 user.options = options
1059 user.path = copy(path)
1060 user.appName = appName
1061 control:SetCallback("OnRelease", CleanUserData)
1062 control:SetCallback("OnLeave", OptionOnMouseLeave)
1063 control:SetCallback("OnEnter", OptionOnMouseOver)
1068 options - root of the options table being fed
1069 container - widget that controls will be placed in
1070 rootframe - Frame object the options are in
1071 path - table with the keys to get to the group being fed
1074 local function FeedOptions(appName, options,container,rootframe,path,group,inline)
1075 local keySort = new()
1078 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1080 for i = 1, #keySort do
1081 local k = keySort[i]
1084 local hidden = CheckOptionHidden(v, options, path, appName)
1085 local name = GetOptionsMemberValue("name", v, options, path, appName)
1087 if v.type == "group" then
1088 if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
1090 local GroupContainer
1091 if name and name ~= "" then
1092 GroupContainer = gui:Create("InlineGroup")
1093 GroupContainer:SetTitle(name or "")
1095 GroupContainer = gui:Create("SimpleGroup")
1098 GroupContainer.width = "fill"
1099 GroupContainer:SetLayout("flow")
1100 container:AddChild(GroupContainer)
1101 FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
1107 local name = GetOptionsMemberValue("name", v, options, path, appName)
1109 if v.type == "execute" then
1111 local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1112 local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1114 if type(image) == "string" or type(image) == "number" then
1115 control = gui:Create("Icon")
1117 width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1120 height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1122 if type(imageCoords) == "table" then
1123 control:SetImage(image, unpack(imageCoords))
1125 control:SetImage(image)
1127 if type(width) ~= "number" then
1130 if type(height) ~= "number" then
1133 control:SetImageSize(width, height)
1134 control:SetLabel(name)
1136 control = gui:Create("Button")
1137 control:SetText(name)
1139 control:SetCallback("OnClick",ActivateControl)
1141 elseif v.type == "input" then
1142 local controlType = v.dialogControl or v.control or (v.multiline and "MultiLineEditBox") or "EditBox"
1143 control = gui:Create(controlType)
1145 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1146 control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox")
1149 if v.multiline and control.SetNumLines then
1150 control:SetNumLines(tonumber(v.multiline) or 4)
1152 control:SetLabel(name)
1153 control:SetCallback("OnEnterPressed",ActivateControl)
1154 local text = GetOptionsMemberValue("get",v, options, path, appName)
1155 if type(text) ~= "string" then
1158 control:SetText(text)
1160 elseif v.type == "toggle" then
1161 control = gui:Create("CheckBox")
1162 control:SetLabel(name)
1163 control:SetTriState(v.tristate)
1164 local value = GetOptionsMemberValue("get",v, options, path, appName)
1165 control:SetValue(value)
1166 control:SetCallback("OnValueChanged",ActivateControl)
1168 if v.descStyle == "inline" then
1169 local desc = GetOptionsMemberValue("desc", v, options, path, appName)
1170 control:SetDescription(desc)
1173 local image = GetOptionsMemberValue("image", v, options, path, appName)
1174 local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName)
1176 if type(image) == "string" or type(image) == "number" then
1177 if type(imageCoords) == "table" then
1178 control:SetImage(image, unpack(imageCoords))
1180 control:SetImage(image)
1183 elseif v.type == "range" then
1184 control = gui:Create("Slider")
1185 control:SetLabel(name)
1186 control:SetSliderValues(v.softMin or v.min or 0, v.softMax or v.max or 100, v.bigStep or v.step or 0)
1187 control:SetIsPercent(v.isPercent)
1188 local value = GetOptionsMemberValue("get",v, options, path, appName)
1189 if type(value) ~= "number" then
1192 control:SetValue(value)
1193 control:SetCallback("OnValueChanged",ActivateSlider)
1194 control:SetCallback("OnMouseUp",ActivateSlider)
1196 elseif v.type == "select" then
1197 local values = GetOptionsMemberValue("values", v, options, path, appName)
1198 if v.style == "radio" then
1199 local disabled = CheckOptionDisabled(v, options, path, appName)
1200 local width = GetOptionsMemberValue("width",v,options,path,appName)
1201 control = gui:Create("InlineGroup")
1202 control:SetLayout("Flow")
1203 control:SetTitle(name)
1204 control.width = "fill"
1206 control:PauseLayout()
1207 local optionValue = GetOptionsMemberValue("get",v, options, path, appName)
1209 for value, text in pairs(values) do
1213 for k, value in ipairs(t) do
1214 local text = values[value]
1215 local radio = gui:Create("CheckBox")
1216 radio:SetLabel(text)
1217 radio:SetUserData("value", value)
1218 radio:SetUserData("text", text)
1219 radio:SetDisabled(disabled)
1220 radio:SetType("radio")
1221 radio:SetValue(optionValue == value)
1222 radio:SetCallback("OnValueChanged", ActivateMultiControl)
1223 InjectInfo(radio, options, v, path, rootframe, appName)
1224 control:AddChild(radio)
1225 if width == "double" then
1226 radio:SetWidth(width_multiplier * 2)
1227 elseif width == "half" then
1228 radio:SetWidth(width_multiplier / 2)
1229 elseif width == "full" then
1230 radio.width = "fill"
1232 radio:SetWidth(width_multiplier)
1235 control:ResumeLayout()
1238 local controlType = v.dialogControl or v.control or "Dropdown"
1239 control = gui:Create(controlType)
1241 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1242 control = gui:Create("Dropdown")
1244 local itemType = v.itemControl
1245 if itemType and not gui:GetWidgetVersion(itemType) then
1246 geterrorhandler()(("Invalid Custom Item Type - %s"):format(tostring(itemType)))
1249 control:SetLabel(name)
1250 control:SetList(values, nil, itemType)
1251 local value = GetOptionsMemberValue("get",v, options, path, appName)
1252 if not values[value] then
1255 control:SetValue(value)
1256 control:SetCallback("OnValueChanged", ActivateControl)
1259 elseif v.type == "multiselect" then
1260 local values = GetOptionsMemberValue("values", v, options, path, appName)
1261 local disabled = CheckOptionDisabled(v, options, path, appName)
1263 local controlType = v.dialogControl or v.control
1265 local valuesort = new()
1267 for value, text in pairs(values) do
1268 tinsert(valuesort, value)
1274 control = gui:Create(controlType)
1276 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1280 control:SetMultiselect(true)
1281 control:SetLabel(name)
1282 control:SetList(values)
1283 control:SetDisabled(disabled)
1284 control:SetCallback("OnValueChanged",ActivateControl)
1285 control:SetCallback("OnClosed", MultiControlOnClosed)
1286 local width = GetOptionsMemberValue("width",v,options,path,appName)
1287 if width == "double" then
1288 control:SetWidth(width_multiplier * 2)
1289 elseif width == "half" then
1290 control:SetWidth(width_multiplier / 2)
1291 elseif width == "full" then
1292 control.width = "fill"
1294 control:SetWidth(width_multiplier)
1296 --check:SetTriState(v.tristate)
1297 for i = 1, #valuesort do
1298 local key = valuesort[i]
1299 local value = GetOptionsMemberValue("get",v, options, path, appName, key)
1300 control:SetItemValue(key,value)
1303 control = gui:Create("InlineGroup")
1304 control:SetLayout("Flow")
1305 control:SetTitle(name)
1306 control.width = "fill"
1308 control:PauseLayout()
1309 local width = GetOptionsMemberValue("width",v,options,path,appName)
1310 for i = 1, #valuesort do
1311 local value = valuesort[i]
1312 local text = values[value]
1313 local check = gui:Create("CheckBox")
1314 check:SetLabel(text)
1315 check:SetUserData("value", value)
1316 check:SetUserData("text", text)
1317 check:SetDisabled(disabled)
1318 check:SetTriState(v.tristate)
1319 check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
1320 check:SetCallback("OnValueChanged",ActivateMultiControl)
1321 InjectInfo(check, options, v, path, rootframe, appName)
1322 control:AddChild(check)
1323 if width == "double" then
1324 check:SetWidth(width_multiplier * 2)
1325 elseif width == "half" then
1326 check:SetWidth(width_multiplier / 2)
1327 elseif width == "full" then
1328 check.width = "fill"
1330 check:SetWidth(width_multiplier)
1333 control:ResumeLayout()
1341 elseif v.type == "color" then
1342 control = gui:Create("ColorPicker")
1343 control:SetLabel(name)
1344 control:SetHasAlpha(GetOptionsMemberValue("hasAlpha",v, options, path, appName))
1345 control:SetColor(GetOptionsMemberValue("get",v, options, path, appName))
1346 control:SetCallback("OnValueChanged",ActivateControl)
1347 control:SetCallback("OnValueConfirmed",ActivateControl)
1349 elseif v.type == "keybinding" then
1350 control = gui:Create("Keybinding")
1351 control:SetLabel(name)
1352 control:SetKey(GetOptionsMemberValue("get",v, options, path, appName))
1353 control:SetCallback("OnKeyChanged",ActivateControl)
1355 elseif v.type == "header" then
1356 control = gui:Create("Heading")
1357 control:SetText(name)
1358 control.width = "fill"
1360 elseif v.type == "description" then
1361 control = gui:Create("Label")
1362 control:SetText(name)
1364 local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName)
1365 if fontSize == "medium" then
1366 control:SetFontObject(GameFontHighlight)
1367 elseif fontSize == "large" then
1368 control:SetFontObject(GameFontHighlightLarge)
1369 else -- small or invalid
1370 control:SetFontObject(GameFontHighlightSmall)
1373 local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1374 local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1376 if type(image) == "string" or type(image) == "number" then
1378 width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1381 height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1383 if type(imageCoords) == "table" then
1384 control:SetImage(image, unpack(imageCoords))
1386 control:SetImage(image)
1388 if type(width) ~= "number" then
1391 if type(height) ~= "number" then
1394 control:SetImageSize(width, height)
1396 local width = GetOptionsMemberValue("width",v,options,path,appName)
1397 control.width = not width and "fill"
1402 if control.width ~= "fill" then
1403 local width = GetOptionsMemberValue("width",v,options,path,appName)
1404 if width == "double" then
1405 control:SetWidth(width_multiplier * 2)
1406 elseif width == "half" then
1407 control:SetWidth(width_multiplier / 2)
1408 elseif width == "full" then
1409 control.width = "fill"
1411 control:SetWidth(width_multiplier)
1414 if control.SetDisabled then
1415 local disabled = CheckOptionDisabled(v, options, path, appName)
1416 control:SetDisabled(disabled)
1419 InjectInfo(control, options, v, path, rootframe, appName)
1420 container:AddChild(control)
1427 container:ResumeLayout()
1428 container:DoLayout()
1433 local function BuildPath(path, ...)
1434 for i = 1, select("#",...) do
1435 tinsert(path, (select(i,...)))
1440 local function TreeOnButtonEnter(widget, event, uniquevalue, button)
1441 local user = widget:GetUserDataTable()
1442 if not user then return end
1443 local options = user.options
1444 local option = user.option
1445 local path = user.path
1446 local appName = user.appName
1448 local feedpath = new()
1450 feedpath[i] = path[i]
1453 BuildPath(feedpath, ("\001"):split(uniquevalue))
1454 local group = options
1455 for i = 1, #feedpath do
1456 if not group then return end
1457 group = GetSubOption(group, feedpath[i])
1460 local name = GetOptionsMemberValue("name", group, options, feedpath, appName)
1461 local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName)
1463 GameTooltip:SetOwner(button, "ANCHOR_NONE")
1464 if widget.type == "TabGroup" then
1465 GameTooltip:SetPoint("BOTTOM",button,"TOP")
1467 GameTooltip:SetPoint("LEFT",button,"RIGHT")
1470 GameTooltip:SetText(name, 1, .82, 0, true)
1472 if type(desc) == "string" then
1473 GameTooltip:AddLine(desc, 1, 1, 1, true)
1479 local function TreeOnButtonLeave(widget, event, value, button)
1484 local function GroupExists(appName, options, path, uniquevalue)
1485 if not uniquevalue then return false end
1487 local feedpath = new()
1488 local temppath = new()
1490 feedpath[i] = path[i]
1493 BuildPath(feedpath, ("\001"):split(uniquevalue))
1495 local group = options
1496 for i = 1, #feedpath do
1497 local v = feedpath[i]
1499 group = GetSubOption(group, v)
1501 if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then
1512 local function GroupSelected(widget, event, uniquevalue)
1514 local user = widget:GetUserDataTable()
1516 local options = user.options
1517 local option = user.option
1518 local path = user.path
1519 local rootframe = user.rootframe
1521 local feedpath = new()
1523 feedpath[i] = path[i]
1526 BuildPath(feedpath, ("\001"):split(uniquevalue))
1527 local group = options
1528 for i = 1, #feedpath do
1529 group = GetSubOption(group, feedpath[i])
1531 widget:ReleaseChildren()
1532 AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath)
1541 This function will feed one group, and any inline child groups into the given container
1542 Select Groups will only have the selection control (tree, tabs, dropdown) fed in
1543 and have a group selected, this event will trigger the feeding of child groups
1546 If the group is Inline, FeedOptions
1547 If the group has no child groups, FeedOptions
1549 If the group is a tab or select group, FeedOptions then add the Group Control
1550 If the group is a tree group FeedOptions then
1551 its parent isnt a tree group: then add the tree control containing this and all child tree groups
1552 if its parent is a tree group, its already a node on a tree
1555 function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot)
1556 local group = options
1557 --follow the path to get to the curent group
1559 local grouptype, parenttype = options.childGroups, "none"
1564 group = GetSubOption(group, v)
1565 inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1566 parenttype = grouptype
1567 grouptype = group.childGroups
1570 if not parenttype then
1574 --check if the group has child groups
1575 local hasChildGroups
1576 for k, v in pairs(group.args) do
1577 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1578 hasChildGroups = true
1581 if group.plugins then
1582 for plugin, t in pairs(group.plugins) do
1583 for k, v in pairs(t) do
1584 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1585 hasChildGroups = true
1591 container:SetLayout("flow")
1594 --Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on
1595 if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and (parenttype == "tree" and not isRoot)) then
1596 if container.type ~= "InlineGroup" and container.type ~= "SimpleGroup" then
1597 scroll = gui:Create("ScrollFrame")
1598 scroll:SetLayout("flow")
1599 scroll.width = "fill"
1600 scroll.height = "fill"
1601 container:SetLayout("fill")
1602 container:AddChild(scroll)
1607 FeedOptions(appName,options,container,rootframe,path,group,nil)
1610 container:PerformLayout()
1611 local status = self:GetStatusTable(appName, path)
1612 if not status.scroll then
1615 scroll:SetStatusTable(status.scroll)
1618 if hasChildGroups and not inline then
1619 local name = GetOptionsMemberValue("name", group, options, path, appName)
1620 if grouptype == "tab" then
1622 local tab = gui:Create("TabGroup")
1623 InjectInfo(tab, options, group, path, rootframe, appName)
1624 tab:SetCallback("OnGroupSelected", GroupSelected)
1625 tab:SetCallback("OnTabEnter", TreeOnButtonEnter)
1626 tab:SetCallback("OnTabLeave", TreeOnButtonLeave)
1628 local status = AceConfigDialog:GetStatusTable(appName, path)
1629 if not status.groups then
1632 tab:SetStatusTable(status.groups)
1636 local tabs = BuildGroups(group, options, path, appName)
1638 tab:SetUserData("tablist", tabs)
1641 local entry = tabs[i]
1642 if not entry.disabled then
1643 tab:SelectTab((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1648 container:AddChild(tab)
1650 elseif grouptype == "select" then
1652 local select = gui:Create("DropdownGroup")
1653 select:SetTitle(name)
1654 InjectInfo(select, options, group, path, rootframe, appName)
1655 select:SetCallback("OnGroupSelected", GroupSelected)
1656 local status = AceConfigDialog:GetStatusTable(appName, path)
1657 if not status.groups then
1660 select:SetStatusTable(status.groups)
1661 local grouplist, orderlist = BuildSelect(group, options, path, appName)
1662 select:SetGroupList(grouplist, orderlist)
1663 select:SetUserData("grouplist", grouplist)
1664 select:SetUserData("orderlist", orderlist)
1666 local firstgroup = orderlist[1]
1668 select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup)
1671 select.width = "fill"
1672 select.height = "fill"
1674 container:AddChild(select)
1676 --assume tree group by default
1677 --if parenttype is tree then this group is already a node on that tree
1678 elseif (parenttype ~= "tree") or isRoot then
1679 local tree = gui:Create("TreeGroup")
1680 InjectInfo(tree, options, group, path, rootframe, appName)
1681 tree:EnableButtonTooltips(false)
1684 tree.height = "fill"
1686 tree:SetCallback("OnGroupSelected", GroupSelected)
1687 tree:SetCallback("OnButtonEnter", TreeOnButtonEnter)
1688 tree:SetCallback("OnButtonLeave", TreeOnButtonLeave)
1690 local status = AceConfigDialog:GetStatusTable(appName, path)
1691 if not status.groups then
1694 local treedefinition = BuildGroups(group, options, path, appName, true)
1695 tree:SetStatusTable(status.groups)
1697 tree:SetTree(treedefinition)
1698 tree:SetUserData("tree",treedefinition)
1700 for i = 1, #treedefinition do
1701 local entry = treedefinition[i]
1702 if not entry.disabled then
1703 tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1708 container:AddChild(tree)
1713 local old_CloseSpecialWindows
1716 local function RefreshOnUpdate(this)
1717 for appName in pairs(this.closing) do
1718 if AceConfigDialog.OpenFrames[appName] then
1719 AceConfigDialog.OpenFrames[appName]:Hide()
1721 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1722 for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1723 if not widget:IsVisible() then
1724 widget:ReleaseChildren()
1728 this.closing[appName] = nil
1731 if this.closeAll then
1732 for k, v in pairs(AceConfigDialog.OpenFrames) do
1733 if not this.closeAllOverride[k] then
1738 wipe(this.closeAllOverride)
1741 for appName in pairs(this.apps) do
1742 if AceConfigDialog.OpenFrames[appName] then
1743 local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable()
1744 AceConfigDialog:Open(appName, unpack(user.basepath or emptyTbl))
1746 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1747 for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1748 local user = widget:GetUserDataTable()
1749 if widget:IsVisible() then
1750 AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(user.basepath or emptyTbl))
1754 this.apps[appName] = nil
1756 this:SetScript("OnUpdate", nil)
1759 -- Upgrade the OnUpdate script as well, if needed.
1760 if AceConfigDialog.frame:GetScript("OnUpdate") then
1761 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1764 --- Close all open options windows
1765 function AceConfigDialog:CloseAll()
1766 AceConfigDialog.frame.closeAll = true
1767 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1768 if next(self.OpenFrames) then
1773 --- Close a specific options window.
1774 -- @param appName The application name as given to `:RegisterOptionsTable()`
1775 function AceConfigDialog:Close(appName)
1776 if self.OpenFrames[appName] then
1777 AceConfigDialog.frame.closing[appName] = true
1778 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1783 -- Internal -- Called by AceConfigRegistry
1784 function AceConfigDialog:ConfigTableChanged(event, appName)
1785 AceConfigDialog.frame.apps[appName] = true
1786 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1789 reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged")
1791 --- Sets the default size of the options window for a specific application.
1792 -- @param appName The application name as given to `:RegisterOptionsTable()`
1793 -- @param width The default width
1794 -- @param height The default height
1795 function AceConfigDialog:SetDefaultSize(appName, width, height)
1796 local status = AceConfigDialog:GetStatusTable(appName)
1797 if type(width) == "number" and type(height) == "number" then
1798 status.width = width
1799 status.height = height
1803 --- Open an option window at the specified path (if any).
1804 -- This function can optionally feed the group into a pre-created container
1805 -- instead of creating a new container frame.
1806 -- @paramsig appName [, container][, ...]
1807 -- @param appName The application name as given to `:RegisterOptionsTable()`
1808 -- @param container An optional container frame to feed the options into
1809 -- @param ... The path to open after creating the options window (see `:SelectGroup` for details)
1810 function AceConfigDialog:Open(appName, container, ...)
1811 if not old_CloseSpecialWindows then
1812 old_CloseSpecialWindows = CloseSpecialWindows
1813 CloseSpecialWindows = function()
1814 local found = old_CloseSpecialWindows()
1815 return self:CloseAll() or found
1818 local app = reg:GetOptionsTable(appName)
1820 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
1822 local options = app("dialog", MAJOR)
1827 local name = GetOptionsMemberValue("name", options, options, path, appName)
1829 --If an optional path is specified add it to the path table before feeding the options
1830 --as container is optional as well it may contain the first element of the path
1831 if type(container) == "string" then
1832 tinsert(path, container)
1835 for n = 1, select("#",...) do
1836 tinsert(path, (select(n, ...)))
1839 local option = options
1840 if type(container) == "table" and container.type == "BlizOptionsGroup" and #path > 0 then
1842 option = options.args[path[i]]
1844 name = format("%s - %s", name, GetOptionsMemberValue("name", option, options, path, appName))
1847 --if a container is given feed into that
1851 f:SetUserData("appName", appName)
1852 f:SetUserData("iscustom", true)
1854 f:SetUserData("basepath", copy(path))
1856 local status = AceConfigDialog:GetStatusTable(appName)
1857 if not status.width then
1860 if not status.height then
1863 if f.SetStatusTable then
1864 f:SetStatusTable(status)
1867 f:SetTitle(name or "")
1870 if not self.OpenFrames[appName] then
1871 f = gui:Create("Frame")
1872 self.OpenFrames[appName] = f
1874 f = self.OpenFrames[appName]
1877 f:SetCallback("OnClose", FrameOnClose)
1878 f:SetUserData("appName", appName)
1880 f:SetUserData("basepath", copy(path))
1882 f:SetTitle(name or "")
1883 local status = AceConfigDialog:GetStatusTable(appName)
1884 f:SetStatusTable(status)
1887 self:FeedGroup(appName,options,f,f,path,true)
1893 if AceConfigDialog.frame.closeAll then
1894 -- close all is set, but thats not good, since we're just opening here, so force it
1895 AceConfigDialog.frame.closeAllOverride[appName] = true
1899 -- convert pre-39 BlizOptions structure to the new format
1900 if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then
1901 local old = AceConfigDialog.BlizOptions
1903 for key, widget in pairs(old) do
1904 local appName = widget:GetUserData("appName")
1905 if not new[appName] then new[appName] = {} end
1906 new[appName][key] = widget
1908 AceConfigDialog.BlizOptions = new
1910 AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {}
1913 local function FeedToBlizPanel(widget, event)
1914 local path = widget:GetUserData("path")
1915 AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl))
1918 local function ClearBlizPanel(widget, event)
1919 local appName = widget:GetUserData("appName")
1920 AceConfigDialog.frame.closing[appName] = true
1921 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1924 --- Add an option table into the Blizzard Interface Options panel.
1925 -- You can optionally supply a descriptive name to use and a parent frame to use,
1926 -- as well as a path in the options table.\\
1927 -- If no name is specified, the appName will be used instead.
1929 -- If you specify a proper `parent` (by name), the interface options will generate a
1930 -- tree layout. Note that only one level of children is supported, so the parent always
1931 -- has to be a head-level note.
1933 -- This function returns a reference to the container frame registered with the Interface
1934 -- Options. You can use this reference to open the options with the API function
1935 -- `InterfaceOptionsFrame_OpenToCategory`.
1936 -- @param appName The application name as given to `:RegisterOptionsTable()`
1937 -- @param name A descriptive name to display in the options tree (defaults to appName)
1938 -- @param parent The parent to use in the interface options tree.
1939 -- @param ... The path in the options table to feed into the interface options panel.
1940 -- @return The reference to the frame registered into the Interface Options.
1941 function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
1942 local BlizOptions = AceConfigDialog.BlizOptions
1945 for n = 1, select("#", ...) do
1946 key = key.."\001"..select(n, ...)
1949 if not BlizOptions[appName] then
1950 BlizOptions[appName] = {}
1953 if not BlizOptions[appName][key] then
1954 local group = gui:Create("BlizOptionsGroup")
1955 BlizOptions[appName][key] = group
1956 group:SetName(name or appName, parent)
1958 group:SetTitle(name or appName)
1959 group:SetUserData("appName", appName)
1960 if select("#", ...) > 0 then
1962 for n = 1, select("#",...) do
1963 tinsert(path, (select(n, ...)))
1965 group:SetUserData("path", path)
1967 group:SetCallback("OnShow", FeedToBlizPanel)
1968 group:SetCallback("OnHide", ClearBlizPanel)
1969 InterfaceOptions_AddCategory(group.frame)
1972 error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2)