8da63c0 - Add incoming res icon
[wowui.git] / OmaRFConfig / libs / AceConfig-3.0 / AceConfigDialog-3.0 / AceConfigDialog-3.0.lua
1 --- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables.
2 -- @class file
3 -- @name AceConfigDialog-3.0
4 -- @release $Id: AceConfigDialog-3.0.lua 1163 2017-08-14 14:04:39Z nevcairiel $
5
6 local LibStub = LibStub
7 local gui = LibStub("AceGUI-3.0")
8 local reg = LibStub("AceConfigRegistry-3.0")
9
10 local MAJOR, MINOR = "AceConfigDialog-3.0", 64
11 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
12
13 if not AceConfigDialog then return end
14
15 AceConfigDialog.OpenFrames = AceConfigDialog.OpenFrames or {}
16 AceConfigDialog.Status = AceConfigDialog.Status or {}
17 AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame")
18
19 AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {}
20 AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
21 AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {}
22
23 -- Lua APIs
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
30
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
36
37 local emptyTbl = {}
38
39 --[[
40          xpcall safecall implementation
41 ]]
42 local xpcall = xpcall
43
44 local function errorhandler(err)
45         return geterrorhandler()(err)
46 end
47
48 local function CreateDispatcher(argCount)
49         local code = [[
50                 local xpcall, eh = ...
51                 local method, ARGS
52                 local function call() return method(ARGS) end
53         
54                 local function dispatch(func, ...)
55                          method = func
56                          if not method then return end
57                          ARGS = ...
58                          return xpcall(call, eh)
59                 end
60         
61                 return dispatch
62         ]]
63         
64         local ARGS = {}
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)
68 end
69
70 local Dispatchers = setmetatable({}, {__index=function(self, argCount)
71         local dispatcher = CreateDispatcher(argCount)
72         rawset(self, argCount, dispatcher)
73         return dispatcher
74 end})
75 Dispatchers[0] = function(func)
76         return xpcall(func, errorhandler)
77 end
78  
79 local function safecall(func, ...)
80         return Dispatchers[select("#", ...)](func, ...)
81 end
82
83 local width_multiplier = 170
84
85 --[[
86 Group Types
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
89
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
92
93   Select- Same as Tab but with entries in a dropdown rather than tabs
94
95
96   Inline Groups
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
100
101 ]]
102
103 -- Recycling functions
104 local new, del, copy
105 --newcount, delcount,createdcount,cached = 0,0,0
106 do
107         local pool = setmetatable({},{__mode="k"})
108         function new()
109                 --newcount = newcount + 1
110                 local t = next(pool)
111                 if t then
112                         pool[t] = nil
113                         return t
114                 else
115                         --createdcount = createdcount + 1
116                         return {}
117                 end
118         end
119         function copy(t)
120                 local c = new()
121                 for k, v in pairs(t) do
122                         c[k] = v
123                 end
124                 return c
125         end
126         function del(t)
127                 --delcount = delcount + 1
128                 wipe(t)
129                 pool[t] = true
130         end
131 --      function cached()
132 --              local n = 0
133 --              for k in pairs(pool) do
134 --                      n = n + 1
135 --              end
136 --              return n
137 --      end
138 end
139
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
144       return select(i,...)
145     end
146   end
147 end
148
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
153                         if t[key] then
154                                 return t[key]
155                         end
156                 end
157         end
158
159         return group.args[key]
160 end
161
162 --Option member type definitions, used to decide how to access it
163
164 --Is the member Inherited from parent options
165 local isInherited = {
166         set = true,
167         get = true,
168         func = true,
169         confirm = true,
170         validate = true,
171         disabled = true,
172         hidden = true
173 }
174
175 --Does a string type mean a literal value, instead of the default of a method of the handler
176 local stringIsLiteral = {
177         name = true,
178         desc = true,
179         icon = true,
180         usage = true,
181         width = true,
182         image = true,
183         fontSize = true,
184 }
185
186 --Is Never a function or method
187 local allIsLiteral = {
188         type = true,
189         descStyle = true,
190         imageWidth = true,
191         imageHeight = true,
192 }
193
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]
200         
201
202         --get the member of the option, traversing the tree if it can be inherited
203         local member
204         
205         if inherits then
206                 local group = options
207                 if group[membername] ~= nil then
208                         member = group[membername]
209                 end
210                 for i = 1, #path do
211                         group = GetSubOption(group, path[i])
212                         if group[membername] ~= nil then
213                                 member = group[membername]
214                         end
215                 end
216         else
217                 member = option[membername]
218         end
219         
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
223                 local info = new()
224                 --traverse the options table, picking up the handler and filling the info with the path
225                 local handler
226                 local group = options
227                 handler = group.handler or handler
228                 
229                 for i = 1, #path do
230                         group = GetSubOption(group, path[i])
231                         info[i] = path[i]
232                         handler = group.handler or handler
233                 end
234                 
235                 info.options = options
236                 info.appName = appName
237                 info[0] = appName
238                 info.arg = option.arg
239                 info.handler = handler
240                 info.option = option
241                 info.type = option.type
242                 info.uiType = "dialog"
243                 info.uiName = MAJOR
244         
245                 local a, b, c ,d 
246                 --using 4 returns for the get of a color type, increase if a type needs more
247                 if type(member) == "function" then
248                         --Call the function
249                         a,b,c,d = member(info, ...)
250                 else
251                         --Call the method
252                         if handler and handler[member] then
253                                 a,b,c,d = handler[member](handler, info, ...)
254                         else
255                                 error(format("Method %s doesn't exist in handler for type %s", member, membername))
256                         end
257                 end
258                 del(info)
259                 return a,b,c,d
260         else
261                 --The value isnt a function to call, return it
262                 return member   
263         end     
264 end
265
266 --[[calls an options function that could be inherited, method name or function ref
267 local function CallOptionsFunction(funcname ,option, options, path, appName, ...)
268         local info = new()
269
270         local func
271         local group = options
272         local handler
273
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]
278         end
279         handler = group.handler or handler
280
281         for i, v in ipairs(path) do
282                 group = GetSubOption(group, v)
283                 info[i] = v
284                 if group[funcname] ~= nil then
285                         func =  group[funcname]
286                 end
287                 handler = group.handler or handler
288         end
289
290         info.options = options
291         info[0] = appName
292         info.arg = option.arg
293
294         local a, b, c ,d
295         if type(func) == "string" then
296                 if handler and handler[func] then
297                         a,b,c,d = handler[func](handler, info, ...)
298                 else
299                         error(string.format("Method %s doesn't exist in handler for type func", func))
300                 end
301         elseif type(func) == "function" then
302                 a,b,c,d = func(info, ...)
303         end
304         del(info)
305         return a,b,c,d
306 end
307 --]]
308
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
311 local tempOrders
312 local tempNames
313
314 local function compareOptions(a,b)
315         if not a then
316                 return true
317         end
318         if not b then
319                 return false
320         end
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()
326         end
327         if OrderA < 0 then
328                 if OrderB > 0 then
329                         return false
330                 end
331         else
332                 if OrderB < 0 then
333                         return true
334                 end
335         end
336         return OrderA < OrderB
337 end
338
339
340
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)
345         tempOrders = new()
346         tempNames = new()
347         
348         if group.plugins then
349                 for plugin, t in pairs(group.plugins) do
350                         for k, v in pairs(t) do
351                                 if not opts[k] then
352                                         tinsert(keySort, k)
353                                         opts[k] = v
354
355                                         path[#path+1] = k
356                                         tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
357                                         tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
358                                         path[#path] = nil
359                                 end
360                         end
361                 end
362         end
363         
364         for k, v in pairs(group.args) do
365                 if not opts[k] then
366                         tinsert(keySort, k)
367                         opts[k] = v
368
369                         path[#path+1] = k
370                         tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
371                         tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
372                         path[#path] = nil
373                 end
374         end
375
376         tsort(keySort, compareOptions)
377
378         del(tempOrders)
379         del(tempNames)
380 end
381
382 local function DelTree(tree)
383         if tree.children then
384                 local childs = tree.children
385                 for i = 1, #childs do
386                         DelTree(childs[i])
387                         del(childs[i])
388                 end
389                 del(childs)
390         end
391 end
392
393 local function CleanUserData(widget, event)
394         
395         local user = widget:GetUserDataTable()
396
397         if user.path then
398                 del(user.path)
399         end
400
401         if widget.type == "TreeGroup" then
402                 local tree = user.tree
403                 widget:SetTree(nil)
404                 if tree then
405                         for i = 1, #tree do
406                                 DelTree(tree[i])
407                                 del(tree[i])
408                         end
409                         del(tree)
410                 end
411         end
412
413         if widget.type == "TabGroup" then
414                 widget:SetTabs(nil)
415                 if user.tablist then
416                         del(user.tablist)
417                 end
418         end
419
420         if widget.type == "DropdownGroup" then
421                 widget:SetGroupList(nil)
422                 if user.grouplist then
423                         del(user.grouplist)
424                 end
425                 if user.orderlist then
426                         del(user.orderlist)
427                 end
428         end
429 end
430
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)
434 -- @return 
435 function AceConfigDialog:GetStatusTable(appName, path)
436         local status = self.Status
437
438         if not status[appName] then
439                 status[appName] = {}
440                 status[appName].status = {}
441                 status[appName].children = {}
442         end
443
444         status = status[appName]
445
446         if path then
447                 for i = 1, #path do
448                         local v = path[i]
449                         if not status.children[v] then
450                                 status.children[v] = {}
451                                 status.children[v].status = {}
452                                 status.children[v].children = {}
453                         end
454                         status = status.children[v]
455                 end
456         end
457
458         return status.status
459 end
460
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, ...)
466         local path = new()
467
468         
469         local app = reg:GetOptionsTable(appName)
470         if not app then
471                 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
472         end
473         local options = app("dialog", MAJOR)
474         local group = options
475         local status = self:GetStatusTable(appName, path)
476         if not status.groups then
477                 status.groups = {}
478         end
479         status = status.groups
480         local treevalue 
481         local treestatus 
482         
483         for n = 1, select("#",...) do
484                 local key = select(n, ...)
485
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
490                         treevalue = nil
491                 else
492                         --tree group by default
493                         if treevalue then
494                                 --this is an extra level of a tree group, build a uniquevalue for it
495                                 treevalue = treevalue.."\001"..key
496                         else
497                                 --this is the top level of a tree group, the uniquevalue is the same as the key
498                                 treevalue = key
499                                 if not status.groups then
500                                         status.groups = {}
501                                 end
502                                 --save this trees status table for any extra levels or groups
503                                 treestatus = status
504                         end
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
509                         
510                 end
511                 
512                 --move to the next group in the path
513                 group = GetSubOption(group, key)
514                 if not group then 
515                         break
516                 end
517                 tinsert(path, key)
518                 status = self:GetStatusTable(appName, path)
519                 if not status.groups then
520                         status.groups = {}
521                 end
522                 status = status.groups
523         end
524         
525         del(path)
526         reg:NotifyChange(appName)
527 end     
528
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
536
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
542         
543         if descStyle and descStyle ~= "tooltip" then return end
544         
545         GameTooltip:SetText(name, 1, .82, 0, true)
546         
547         if opt.type == "multiselect" then
548                 GameTooltip:AddLine(user.text, 0.5, 0.5, 0.8, true)
549         end     
550         if type(desc) == "string" then
551                 GameTooltip:AddLine(desc, 1, 1, 1, true)
552         end
553         if type(usage) == "string" then
554                 GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true)
555         end
556
557         GameTooltip:Show()
558 end
559
560 local function OptionOnMouseLeave(widget, event)
561         GameTooltip:Hide()
562 end
563
564 local function GetFuncName(option)
565         local type = option.type
566         if type == "execute" then
567                 return "func"
568         else
569                 return "set"
570         end
571 end
572 local function confirmPopup(appName, rootframe, basepath, info, message, func, ...)
573         if not StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] then
574                 StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] = {}
575         end
576         local t = StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"]
577         for k in pairs(t) do
578                 t[k] = nil
579         end
580         t.text = message
581         t.button1 = ACCEPT
582         t.button2 = CANCEL
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)
589                 end
590                 AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
591                 del(info)
592         end
593         t.OnCancel = function()
594                 if dialog and oldstrata then
595                         dialog:SetFrameStrata(oldstrata)
596                 end
597                 AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
598                 del(info)
599         end
600         for i = 1, select("#", ...) do
601                 t[i] = select(i, ...) or false
602         end
603         t.timeout = 0
604         t.whileDead = 1
605         t.hideOnEscape = 1
606
607         dialog = StaticPopup_Show("ACECONFIGDIALOG30_CONFIRM_DIALOG")
608         if dialog then
609                 oldstrata = dialog:GetFrameStrata()
610                 dialog:SetFrameStrata("TOOLTIP")
611         end
612 end
613
614 local function validationErrorPopup(message)
615         if not StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] then
616                 StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"] = {}
617         end
618         local t = StaticPopupDialogs["ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG"]
619         t.text = message
620         t.button1 = OKAY
621         t.preferredIndex = STATICPOPUP_NUMDIALOGS
622         local dialog, oldstrata
623         t.OnAccept = function()
624                 if dialog and oldstrata then
625                         dialog:SetFrameStrata(oldstrata)
626                 end
627         end
628         t.timeout = 0
629         t.whileDead = 1
630         t.hideOnEscape = 1
631
632         dialog = StaticPopup_Show("ACECONFIGDIALOG30_VALIDATION_ERROR_DIALOG")
633         if dialog then
634                 oldstrata = dialog:GetFrameStrata()
635                 dialog:SetFrameStrata("TOOLTIP")
636         end
637 end
638
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
646         local info = new()
647
648         local func
649         local group = options
650         local funcname = GetFuncName(option)
651         local handler
652         local confirm
653         local validate
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]
658         end
659         handler = group.handler or handler
660         confirm = group.confirm
661         validate = group.validate
662         for i = 1, #path do
663                 local v = path[i]
664                 group = GetSubOption(group, v)
665                 info[i] = v
666                 if group[funcname] ~= nil then
667                         func =  group[funcname]
668                 end
669                 handler = group.handler or handler
670                 if group.confirm ~= nil then
671                         confirm = group.confirm
672                 end
673                 if group.validate ~= nil then
674                         validate = group.validate
675                 end
676         end
677
678         info.options = options
679         info.appName = user.appName
680         info.arg = option.arg
681         info.handler = handler
682         info.option = option
683         info.type = option.type
684         info.uiType = "dialog"
685         info.uiName = MAJOR
686
687         local name
688         if type(option.name) == "function" then
689                 name = option.name(info)
690         elseif type(option.name) == "string" then
691                 name = option.name
692         else
693                 name = ""
694         end
695         local usage = option.usage
696         local pattern = option.pattern
697
698         local validated = true
699
700         if option.type == "input" then
701                 if type(pattern)=="string" then
702                         if not strmatch(..., pattern) then
703                                 validated = false
704                         end
705                 end
706         end
707         
708         local success
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
714                         else
715                                 error(format("Method %s doesn't exist in handler for type execute", validate))
716                         end
717                 elseif type(validate) == "function" then
718                         success, validated = safecall(validate, info, ...)
719                         if not success then validated = false end
720                 end
721         end
722         
723         local rootframe = user.rootframe
724         if not validated or type(validated) == "string" then
725                 if not validated then
726                         if usage then
727                                 validated = name..": "..usage
728                         else
729                                 if pattern then
730                                         validated = name..": Expected "..pattern
731                                 else
732                                         validated = name..": Invalid Value"
733                                 end
734                         end
735                 end
736
737                 -- show validate message
738                 if rootframe.SetStatusText then
739                         rootframe:SetStatusText(validated)
740                 else
741                         validationErrorPopup(validated)
742                 end
743                 PlaySound(PlaySoundKitID and "igPlayerInviteDecline" or 882) -- SOUNDKIT.IG_PLAYER_INVITE_DECLINE || XXX _DECLINE is actually missing from the table
744                 del(info)
745                 return true
746         else
747                 
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
755                                         confirm = true
756                                 elseif not success then
757                                         confirm = false
758                                 end
759                         else
760                                 error(format("Method %s doesn't exist in handler for type confirm", confirm))
761                         end
762                 elseif type(confirm) == "function" then
763                         success, confirm = safecall(confirm, info, ...)
764                         if success and type(confirm) == "string" then
765                                 confirmText = confirm
766                                 confirm = true
767                         elseif not success then
768                                 confirm = false
769                         end
770                 end
771
772                 --confirm if needed
773                 if type(confirm) == "boolean" then
774                         if confirm then
775                                 if not confirmText then
776                                         local name, desc = option.name, option.desc
777                                         if type(name) == "function" then
778                                                 name = name(info)
779                                         end
780                                         if type(desc) == "function" then
781                                                 desc = desc(info)
782                                         end
783                                         confirmText = name
784                                         if desc then
785                                                 confirmText = confirmText.." - "..desc
786                                         end
787                                 end
788                                 
789                                 local iscustom = user.rootframe:GetUserData("iscustom")
790                                 local rootframe
791                                 
792                                 if iscustom then
793                                         rootframe = user.rootframe
794                                 end
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, ...)
799                                         else
800                                                 error(format("Method %s doesn't exist in handler for type func", func))
801                                         end
802                                 elseif type(func) == "function" then
803                                         confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...)
804                                 end
805                                 --func will be called and info deleted when the confirm dialog is responded to
806                                 return
807                         end
808                 end
809
810                 --call the function
811                 if type(func) == "string" then
812                         if handler and handler[func] then
813                                 safecall(handler[func],handler, info, ...)
814                         else
815                                 error(format("Method %s doesn't exist in handler for type func", func))
816                         end
817                 elseif type(func) == "function" then
818                         safecall(func,info, ...)
819                 end
820
821
822
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
828                                 
829                                 if iscustom then
830                                         AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
831                                 else
832                                         AceConfigDialog:Open(user.appName, unpack(basepath))
833                                 end
834                         end
835                 elseif option.type == "range" then
836                         if event == "OnMouseUp" then
837                                 if iscustom then
838                                         AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
839                                 else
840                                         AceConfigDialog:Open(user.appName, unpack(basepath))
841                                 end
842                         end
843                 --multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed'
844                 elseif option.type == "multiselect" then
845                         user.valuechanged = true
846                 else
847                         if iscustom then
848                                 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
849                         else
850                                 AceConfigDialog:Open(user.appName, unpack(basepath))
851                         end
852                 end
853
854         end
855         del(info)
856 end
857
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
861         if min then
862                 if step then
863                         value = math_floor((value - min) / step + 0.5) * step + min
864                 end
865                 value = math_max(value, min)
866         end
867         if max then
868                 value = math_min(value, max)
869         end
870         ActivateControl(widget,event,value)
871 end
872
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
880         if iscustom then
881                 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
882         else
883                 AceConfigDialog:Open(user.appName, unpack(basepath))
884         end
885 end
886
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
892                 if iscustom then
893                         AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
894                 else
895                         AceConfigDialog:Open(user.appName, unpack(basepath))
896                 end
897         end
898 end
899
900 local function FrameOnClose(widget, event)
901         local appName = widget:GetUserData("appName")
902         AceConfigDialog.OpenFrames[appName] = nil
903         gui:Release(widget)
904 end
905
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
910                 return hidden
911         end
912
913         return GetOptionsMemberValue("hidden", option, options, path, appName)
914 end
915
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
920                 return disabled
921         end
922
923         return GetOptionsMemberValue("disabled", option, options, path, appName)
924 end
925 --[[
926 local function BuildTabs(group, options, path, appName)
927         local tabs = new()
928         local text = new()
929         local keySort = new()
930         local opts = new()
931
932         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
933
934         for i = 1, #keySort do
935                 local k = keySort[i]
936                 local v = opts[k]
937                 if v.type == "group" then
938                         path[#path+1] = k
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
942                                 tinsert(tabs, k)
943                                 text[k] = GetOptionsMemberValue("name", v, options, path, appName)
944                         end
945                         path[#path] = nil
946                 end
947         end
948
949         del(keySort)
950         del(opts)
951
952         return tabs, text
953 end
954 ]]
955 local function BuildSelect(group, options, path, appName)
956         local groups = new()
957         local order = new()
958         local keySort = new()
959         local opts = new()
960
961         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
962
963         for i = 1, #keySort do
964                 local k = keySort[i]
965                 local v = opts[k]
966                 if v.type == "group" then
967                         path[#path+1] = k
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)
972                                 tinsert(order, k)
973                         end
974                         path[#path] = nil
975                 end
976         end
977
978         del(opts)
979         del(keySort)
980
981         return groups, order
982 end
983
984 local function BuildSubGroups(group, tree, options, path, appName)
985         local keySort = new()
986         local opts = new()
987
988         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
989
990         for i = 1, #keySort do
991                 local k = keySort[i]
992                 local v = opts[k]
993                 if v.type == "group" then
994                         path[#path+1] = k
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
998                                 local entry = new()
999                                 entry.value = k
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)
1008                                 end
1009                         end
1010                         path[#path] = nil
1011                 end
1012         end
1013
1014         del(keySort)
1015         del(opts)
1016 end
1017
1018 local function BuildGroups(group, options, path, appName, recurse)
1019         local tree = new()
1020         local keySort = new()
1021         local opts = new()
1022
1023         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1024
1025         for i = 1, #keySort do
1026                 local k = keySort[i]
1027                 local v = opts[k]
1028                 if v.type == "group" then
1029                         path[#path+1] = k
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
1033                                 local entry = new()
1034                                 entry.value = k
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)
1038                                 tinsert(tree,entry)
1039                                 if recurse and (v.childGroups or "tree") == "tree" then
1040                                         BuildSubGroups(v,entry, options, path, appName)
1041                                 end
1042                         end
1043                         path[#path] = nil
1044                 end
1045         end
1046         del(keySort)
1047         del(opts)
1048         return tree
1049 end
1050
1051 local function InjectInfo(control, options, option, path, rootframe, appName)
1052         local user = control:GetUserDataTable()
1053         for i = 1, #path do
1054                 user[i] = path[i]
1055         end
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)
1064 end
1065
1066
1067 --[[
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
1072 --]]
1073
1074 local function FeedOptions(appName, options,container,rootframe,path,group,inline)
1075         local keySort = new()
1076         local opts = new()
1077
1078         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1079
1080         for i = 1, #keySort do
1081                 local k = keySort[i]
1082                 local v = opts[k]
1083                 tinsert(path, k)
1084                 local hidden = CheckOptionHidden(v, options, path, appName)
1085                 local name = GetOptionsMemberValue("name", v, options, path, appName)
1086                 if not hidden then
1087                         if v.type == "group" then
1088                                 if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
1089                                         --Inline group
1090                                         local GroupContainer
1091                                         if name and name ~= "" then
1092                                                 GroupContainer = gui:Create("InlineGroup")
1093                                                 GroupContainer:SetTitle(name or "")
1094                                         else
1095                                                 GroupContainer = gui:Create("SimpleGroup")
1096                                         end
1097                                         
1098                                         GroupContainer.width = "fill"
1099                                         GroupContainer:SetLayout("flow")
1100                                         container:AddChild(GroupContainer)
1101                                         FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
1102                                 end
1103                         else
1104                                 --Control to feed
1105                                 local control
1106                                 
1107                                 local name = GetOptionsMemberValue("name", v, options, path, appName)
1108                                 
1109                                 if v.type == "execute" then
1110                                         
1111                                         local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1112                                         local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1113                                         
1114                                         if type(image) == "string" or type(image) == "number" then
1115                                                 control = gui:Create("Icon")
1116                                                 if not width then
1117                                                         width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1118                                                 end
1119                                                 if not height then
1120                                                         height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1121                                                 end
1122                                                 if type(imageCoords) == "table" then
1123                                                         control:SetImage(image, unpack(imageCoords))
1124                                                 else
1125                                                         control:SetImage(image)
1126                                                 end
1127                                                 if type(width) ~= "number" then
1128                                                         width = 32
1129                                                 end
1130                                                 if type(height) ~= "number" then
1131                                                         height = 32
1132                                                 end
1133                                                 control:SetImageSize(width, height)
1134                                                 control:SetLabel(name)
1135                                         else
1136                                                 control = gui:Create("Button")
1137                                                 control:SetText(name)
1138                                         end
1139                                         control:SetCallback("OnClick",ActivateControl)
1140
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)
1144                                         if not control then
1145                                                 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1146                                                 control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox")
1147                                         end
1148                                         
1149                                         if v.multiline and control.SetNumLines then
1150                                                 control:SetNumLines(tonumber(v.multiline) or 4)
1151                                         end
1152                                         control:SetLabel(name)
1153                                         control:SetCallback("OnEnterPressed",ActivateControl)
1154                                         local text = GetOptionsMemberValue("get",v, options, path, appName)
1155                                         if type(text) ~= "string" then
1156                                                 text = ""
1157                                         end
1158                                         control:SetText(text)
1159
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)
1167                                         
1168                                         if v.descStyle == "inline" then
1169                                                 local desc = GetOptionsMemberValue("desc", v, options, path, appName)
1170                                                 control:SetDescription(desc)
1171                                         end
1172                                         
1173                                         local image = GetOptionsMemberValue("image", v, options, path, appName)
1174                                         local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName)
1175                                         
1176                                         if type(image) == "string" or type(image) == "number" then
1177                                                 if type(imageCoords) == "table" then
1178                                                         control:SetImage(image, unpack(imageCoords))
1179                                                 else
1180                                                         control:SetImage(image)
1181                                                 end
1182                                         end
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
1190                                                 value = 0
1191                                         end
1192                                         control:SetValue(value)
1193                                         control:SetCallback("OnValueChanged",ActivateSlider)
1194                                         control:SetCallback("OnMouseUp",ActivateSlider)
1195
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"
1205
1206                                                 control:PauseLayout()
1207                                                 local optionValue = GetOptionsMemberValue("get",v, options, path, appName)
1208                                                 local t = {}
1209                                                 for value, text in pairs(values) do
1210                                                         t[#t+1]=value
1211                                                 end
1212                                                 tsort(t)
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"
1231                                                         else
1232                                                                 radio:SetWidth(width_multiplier)
1233                                                         end
1234                                                 end
1235                                                 control:ResumeLayout()
1236                                                 control:DoLayout()
1237                                         else
1238                                                 local controlType = v.dialogControl or v.control or "Dropdown"
1239                                                 control = gui:Create(controlType)
1240                                                 if not control then
1241                                                         geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1242                                                         control = gui:Create("Dropdown")
1243                                                 end
1244                                                 local itemType = v.itemControl
1245                                                 if itemType and not gui:GetWidgetVersion(itemType) then
1246                                                         geterrorhandler()(("Invalid Custom Item Type - %s"):format(tostring(itemType)))
1247                                                         itemType = nil
1248                                                 end
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
1253                                                         value = nil
1254                                                 end
1255                                                 control:SetValue(value)
1256                                                 control:SetCallback("OnValueChanged", ActivateControl)
1257                                         end
1258
1259                                 elseif v.type == "multiselect" then
1260                                         local values = GetOptionsMemberValue("values", v, options, path, appName)
1261                                         local disabled = CheckOptionDisabled(v, options, path, appName)
1262                                         
1263                                         local controlType = v.dialogControl or v.control
1264                                         
1265                                         local valuesort = new()
1266                                         if values then
1267                                                 for value, text in pairs(values) do
1268                                                         tinsert(valuesort, value)
1269                                                 end
1270                                         end
1271                                         tsort(valuesort)
1272                                         
1273                                         if controlType then
1274                                                 control = gui:Create(controlType)
1275                                                 if not control then
1276                                                         geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1277                                                 end
1278                                         end
1279                                         if control then
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"
1293                                                 else
1294                                                         control:SetWidth(width_multiplier)
1295                                                 end
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)
1301                                                 end
1302                                         else
1303                                                 control = gui:Create("InlineGroup")
1304                                                 control:SetLayout("Flow")
1305                                                 control:SetTitle(name)
1306                                                 control.width = "fill"
1307
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"
1329                                                         else
1330                                                                 check:SetWidth(width_multiplier)
1331                                                         end
1332                                                 end
1333                                                 control:ResumeLayout()
1334                                                 control:DoLayout()
1335
1336                                                 
1337                                         end
1338                                         
1339                                         del(valuesort)
1340
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)
1348
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)
1354
1355                                 elseif v.type == "header" then
1356                                         control = gui:Create("Heading")
1357                                         control:SetText(name)
1358                                         control.width = "fill"
1359
1360                                 elseif v.type == "description" then
1361                                         control = gui:Create("Label")
1362                                         control:SetText(name)
1363                                         
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)
1371                                         end
1372                                         
1373                                         local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1374                                         local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1375                                         
1376                                         if type(image) == "string" or type(image) == "number" then
1377                                                 if not width then
1378                                                         width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1379                                                 end
1380                                                 if not height then
1381                                                         height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1382                                                 end
1383                                                 if type(imageCoords) == "table" then
1384                                                         control:SetImage(image, unpack(imageCoords))
1385                                                 else
1386                                                         control:SetImage(image)
1387                                                 end
1388                                                 if type(width) ~= "number" then
1389                                                         width = 32
1390                                                 end
1391                                                 if type(height) ~= "number" then
1392                                                         height = 32
1393                                                 end
1394                                                 control:SetImageSize(width, height)
1395                                         end
1396                                         local width = GetOptionsMemberValue("width",v,options,path,appName)
1397                                         control.width = not width and "fill"
1398                                 end
1399
1400                                 --Common Init
1401                                 if control then
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"
1410                                                 else
1411                                                         control:SetWidth(width_multiplier)
1412                                                 end
1413                                         end
1414                                         if control.SetDisabled then
1415                                                 local disabled = CheckOptionDisabled(v, options, path, appName)
1416                                                 control:SetDisabled(disabled)
1417                                         end
1418
1419                                         InjectInfo(control, options, v, path, rootframe, appName)
1420                                         container:AddChild(control)
1421                                 end
1422                                 
1423                         end
1424                 end
1425                 tremove(path)
1426         end
1427         container:ResumeLayout()
1428         container:DoLayout()
1429         del(keySort)
1430         del(opts)
1431 end
1432
1433 local function BuildPath(path, ...)
1434         for i = 1, select("#",...)  do
1435                 tinsert(path, (select(i,...)))
1436         end
1437 end
1438
1439
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
1447         
1448         local feedpath = new()
1449         for i = 1, #path do
1450                 feedpath[i] = path[i]
1451         end
1452
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])
1458         end
1459
1460         local name = GetOptionsMemberValue("name", group, options, feedpath, appName)
1461         local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName)
1462         
1463         GameTooltip:SetOwner(button, "ANCHOR_NONE")
1464         if widget.type == "TabGroup" then
1465                 GameTooltip:SetPoint("BOTTOM",button,"TOP")
1466         else
1467                 GameTooltip:SetPoint("LEFT",button,"RIGHT")
1468         end
1469
1470         GameTooltip:SetText(name, 1, .82, 0, true)
1471         
1472         if type(desc) == "string" then
1473                 GameTooltip:AddLine(desc, 1, 1, 1, true)
1474         end
1475         
1476         GameTooltip:Show()
1477 end
1478
1479 local function TreeOnButtonLeave(widget, event, value, button)
1480         GameTooltip:Hide()
1481 end
1482
1483
1484 local function GroupExists(appName, options, path, uniquevalue)
1485         if not uniquevalue then return false end
1486         
1487         local feedpath = new()
1488         local temppath = new()
1489         for i = 1, #path do
1490                 feedpath[i] = path[i]
1491         end
1492         
1493         BuildPath(feedpath, ("\001"):split(uniquevalue))
1494         
1495         local group = options
1496         for i = 1, #feedpath do
1497                 local v = feedpath[i]
1498                 temppath[i] = v
1499                 group = GetSubOption(group, v)
1500                 
1501                 if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then 
1502                         del(feedpath)
1503                         del(temppath)
1504                         return false 
1505                 end
1506         end
1507         del(feedpath)
1508         del(temppath)
1509         return true
1510 end
1511
1512 local function GroupSelected(widget, event, uniquevalue)
1513
1514         local user = widget:GetUserDataTable()
1515
1516         local options = user.options
1517         local option = user.option
1518         local path = user.path
1519         local rootframe = user.rootframe
1520
1521         local feedpath = new()
1522         for i = 1, #path do
1523                 feedpath[i] = path[i]
1524         end
1525
1526         BuildPath(feedpath, ("\001"):split(uniquevalue))
1527         local group = options
1528         for i = 1, #feedpath do
1529                 group = GetSubOption(group, feedpath[i])
1530         end
1531         widget:ReleaseChildren()
1532         AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath)
1533
1534         del(feedpath)
1535 end
1536
1537
1538
1539 --[[
1540 -- INTERNAL --
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
1544
1545 Rules:
1546         If the group is Inline, FeedOptions
1547         If the group has no child groups, FeedOptions
1548
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
1553 --]]
1554
1555 function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot)
1556         local group = options
1557         --follow the path to get to the curent group
1558         local inline
1559         local grouptype, parenttype = options.childGroups, "none"
1560
1561
1562         for i = 1, #path do
1563                 local v = path[i]
1564                 group = GetSubOption(group, v)
1565                 inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1566                 parenttype = grouptype
1567                 grouptype = group.childGroups
1568         end
1569
1570         if not parenttype then
1571                 parenttype = "tree"
1572         end
1573
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
1579                 end
1580         end
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
1586                                 end
1587                         end
1588                 end
1589         end
1590
1591         container:SetLayout("flow")
1592         local scroll
1593
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)
1603                         container = scroll
1604                 end
1605         end
1606
1607         FeedOptions(appName,options,container,rootframe,path,group,nil)
1608
1609         if scroll then
1610                 container:PerformLayout()
1611                 local status = self:GetStatusTable(appName, path)
1612                 if not status.scroll then
1613                         status.scroll = {}
1614                 end
1615                 scroll:SetStatusTable(status.scroll)
1616         end
1617
1618         if hasChildGroups and not inline then
1619                 local name = GetOptionsMemberValue("name", group, options, path, appName)
1620                 if grouptype == "tab" then
1621
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)
1627                         
1628                         local status = AceConfigDialog:GetStatusTable(appName, path)
1629                         if not status.groups then
1630                                 status.groups = {}
1631                         end
1632                         tab:SetStatusTable(status.groups)
1633                         tab.width = "fill"
1634                         tab.height = "fill"
1635
1636                         local tabs = BuildGroups(group, options, path, appName)
1637                         tab:SetTabs(tabs)
1638                         tab:SetUserData("tablist", tabs)
1639
1640                         for i = 1, #tabs do
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)
1644                                         break
1645                                 end
1646                         end
1647                         
1648                         container:AddChild(tab)
1649
1650                 elseif grouptype == "select" then
1651
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
1658                                 status.groups = {}
1659                         end
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)
1665
1666                         local firstgroup = orderlist[1]
1667                         if firstgroup then
1668                                 select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup)
1669                         end
1670                         
1671                         select.width = "fill"
1672                         select.height = "fill"
1673
1674                         container:AddChild(select)
1675
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)
1682                         
1683                         tree.width = "fill"
1684                         tree.height = "fill"
1685
1686                         tree:SetCallback("OnGroupSelected", GroupSelected)
1687                         tree:SetCallback("OnButtonEnter", TreeOnButtonEnter)
1688                         tree:SetCallback("OnButtonLeave", TreeOnButtonLeave)
1689                         
1690                         local status = AceConfigDialog:GetStatusTable(appName, path)
1691                         if not status.groups then
1692                                 status.groups = {}
1693                         end
1694                         local treedefinition = BuildGroups(group, options, path, appName, true)
1695                         tree:SetStatusTable(status.groups)
1696
1697                         tree:SetTree(treedefinition)
1698                         tree:SetUserData("tree",treedefinition)
1699
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)
1704                                         break
1705                                 end
1706                         end
1707
1708                         container:AddChild(tree)
1709                 end
1710         end
1711 end
1712
1713 local old_CloseSpecialWindows
1714
1715
1716 local function RefreshOnUpdate(this)
1717         for appName in pairs(this.closing) do
1718                 if AceConfigDialog.OpenFrames[appName] then
1719                         AceConfigDialog.OpenFrames[appName]:Hide()
1720                 end
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()
1725                                 end
1726                         end
1727                 end
1728                 this.closing[appName] = nil
1729         end
1730         
1731         if this.closeAll then
1732                 for k, v in pairs(AceConfigDialog.OpenFrames) do
1733                         if not this.closeAllOverride[k] then
1734                                 v:Hide()
1735                         end
1736                 end
1737                 this.closeAll = nil
1738                 wipe(this.closeAllOverride)
1739         end
1740         
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))
1745                 end
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))
1751                                 end
1752                         end
1753                 end
1754                 this.apps[appName] = nil
1755         end
1756         this:SetScript("OnUpdate", nil)
1757 end
1758
1759 -- Upgrade the OnUpdate script as well, if needed.
1760 if AceConfigDialog.frame:GetScript("OnUpdate") then
1761         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1762 end
1763
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
1769                 return true
1770         end
1771 end
1772
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)
1779                 return true
1780         end
1781 end
1782
1783 -- Internal -- Called by AceConfigRegistry
1784 function AceConfigDialog:ConfigTableChanged(event, appName)
1785         AceConfigDialog.frame.apps[appName] = true
1786         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1787 end
1788
1789 reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged")
1790
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
1800         end
1801 end
1802
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
1816                 end
1817         end
1818         local app = reg:GetOptionsTable(appName)
1819         if not app then
1820                 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
1821         end
1822         local options = app("dialog", MAJOR)
1823
1824         local f
1825         
1826         local path = new()
1827         local name = GetOptionsMemberValue("name", options, options, path, appName)
1828         
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)
1833                 container = nil
1834         end
1835         for n = 1, select("#",...) do
1836                 tinsert(path, (select(n, ...)))
1837         end
1838         
1839         local option = options
1840         if type(container) == "table" and container.type == "BlizOptionsGroup" and #path > 0 then
1841                 for i = 1, #path do
1842                         option = options.args[path[i]]
1843                 end
1844                 name = format("%s - %s", name, GetOptionsMemberValue("name", option, options, path, appName))
1845         end
1846         
1847         --if a container is given feed into that
1848         if container then
1849                 f = container
1850                 f:ReleaseChildren()
1851                 f:SetUserData("appName", appName)
1852                 f:SetUserData("iscustom", true)
1853                 if #path > 0 then
1854                         f:SetUserData("basepath", copy(path))
1855                 end
1856                 local status = AceConfigDialog:GetStatusTable(appName)
1857                 if not status.width then
1858                         status.width =  700
1859                 end
1860                 if not status.height then
1861                         status.height = 500
1862                 end
1863                 if f.SetStatusTable then
1864                         f:SetStatusTable(status)
1865                 end
1866                 if f.SetTitle then
1867                         f:SetTitle(name or "")
1868                 end
1869         else
1870                 if not self.OpenFrames[appName] then
1871                         f = gui:Create("Frame")
1872                         self.OpenFrames[appName] = f
1873                 else
1874                         f = self.OpenFrames[appName]
1875                 end
1876                 f:ReleaseChildren()
1877                 f:SetCallback("OnClose", FrameOnClose)
1878                 f:SetUserData("appName", appName)
1879                 if #path > 0 then
1880                         f:SetUserData("basepath", copy(path))
1881                 end
1882                 f:SetTitle(name or "")
1883                 local status = AceConfigDialog:GetStatusTable(appName)
1884                 f:SetStatusTable(status)
1885         end
1886
1887         self:FeedGroup(appName,options,f,f,path,true)
1888         if f.Show then
1889                 f:Show()
1890         end
1891         del(path)
1892
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
1896         end
1897 end
1898
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
1902         local new = {}
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
1907         end
1908         AceConfigDialog.BlizOptions = new
1909 else
1910         AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {}
1911 end
1912
1913 local function FeedToBlizPanel(widget, event)
1914         local path = widget:GetUserData("path")
1915         AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl))
1916 end
1917
1918 local function ClearBlizPanel(widget, event)
1919         local appName = widget:GetUserData("appName")
1920         AceConfigDialog.frame.closing[appName] = true
1921         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1922 end
1923
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.
1928 --
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.
1932 --
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
1943         
1944         local key = appName
1945         for n = 1, select("#", ...) do
1946                 key = key.."\001"..select(n, ...)
1947         end
1948         
1949         if not BlizOptions[appName] then
1950                 BlizOptions[appName] = {}
1951         end
1952         
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)
1957
1958                 group:SetTitle(name or appName)
1959                 group:SetUserData("appName", appName)
1960                 if select("#", ...) > 0 then
1961                         local path = {}
1962                         for n = 1, select("#",...) do
1963                                 tinsert(path, (select(n, ...)))
1964                         end
1965                         group:SetUserData("path", path)
1966                 end
1967                 group:SetCallback("OnShow", FeedToBlizPanel)
1968                 group:SetCallback("OnHide", ClearBlizPanel)
1969                 InterfaceOptions_AddCategory(group.frame)
1970                 return group.frame
1971         else
1972                 error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2)
1973         end
1974 end