1c288216581fae94b807d4182a8da6ae9e7665a5
[wowui.git] / 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 1089 2013-09-13 14:32:35Z nevcairiel $
5
6 local LibStub = LibStub
7 local MAJOR, MINOR = "AceConfigDialog-3.0", 58
8 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
9
10 if not AceConfigDialog then return end
11
12 AceConfigDialog.OpenFrames = AceConfigDialog.OpenFrames or {}
13 AceConfigDialog.Status = AceConfigDialog.Status or {}
14 AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame")
15
16 AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {}
17 AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
18 AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {}
19
20 local gui = LibStub("AceGUI-3.0")
21 local reg = LibStub("AceConfigRegistry-3.0")
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, 1)
546         
547         if opt.type == "multiselect" then
548                 GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1)
549         end     
550         if type(desc) == "string" then
551                 GameTooltip:AddLine(desc, 1, 1, 1, 1)
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, 1)
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 ActivateControl(widget, event, ...)
615         --This function will call the set / execute handler for the widget
616         --widget:GetUserDataTable() contains the needed info
617         local user = widget:GetUserDataTable()
618         local option = user.option
619         local options = user.options
620         local path = user.path
621         local info = new()
622
623         local func
624         local group = options
625         local funcname = GetFuncName(option)
626         local handler
627         local confirm
628         local validate
629         --build the info table containing the path
630         -- pick up functions while traversing the tree
631         if group[funcname] ~= nil then
632                 func =  group[funcname]
633         end
634         handler = group.handler or handler
635         confirm = group.confirm
636         validate = group.validate
637         for i = 1, #path do
638                 local v = path[i]
639                 group = GetSubOption(group, v)
640                 info[i] = v
641                 if group[funcname] ~= nil then
642                         func =  group[funcname]
643                 end
644                 handler = group.handler or handler
645                 if group.confirm ~= nil then
646                         confirm = group.confirm
647                 end
648                 if group.validate ~= nil then
649                         validate = group.validate
650                 end
651         end
652
653         info.options = options
654         info.appName = user.appName
655         info.arg = option.arg
656         info.handler = handler
657         info.option = option
658         info.type = option.type
659         info.uiType = "dialog"
660         info.uiName = MAJOR
661
662         local name
663         if type(option.name) == "function" then
664                 name = option.name(info)
665         elseif type(option.name) == "string" then
666                 name = option.name
667         else
668                 name = ""
669         end
670         local usage = option.usage
671         local pattern = option.pattern
672
673         local validated = true
674
675         if option.type == "input" then
676                 if type(pattern)=="string" then
677                         if not strmatch(..., pattern) then
678                                 validated = false
679                         end
680                 end
681         end
682         
683         local success
684         if validated and option.type ~= "execute" then
685                 if type(validate) == "string" then
686                         if handler and handler[validate] then
687                                 success, validated = safecall(handler[validate], handler, info, ...)
688                                 if not success then validated = false end
689                         else
690                                 error(format("Method %s doesn't exist in handler for type execute", validate))
691                         end
692                 elseif type(validate) == "function" then
693                         success, validated = safecall(validate, info, ...)
694                         if not success then validated = false end
695                 end
696         end
697         
698         local rootframe = user.rootframe
699         if type(validated) == "string" then
700                 --validate function returned a message to display
701                 if rootframe.SetStatusText then
702                         rootframe:SetStatusText(validated)
703                 else
704                         -- TODO: do something else.
705                 end
706                 PlaySound("igPlayerInviteDecline")
707                 del(info)
708                 return true
709         elseif not validated then
710                 --validate returned false       
711                 if rootframe.SetStatusText then
712                         if usage then
713                                 rootframe:SetStatusText(name..": "..usage)
714                         else
715                                 if pattern then
716                                         rootframe:SetStatusText(name..": Expected "..pattern)
717                                 else
718                                         rootframe:SetStatusText(name..": Invalid Value")
719                                 end
720                         end
721                 else
722                         -- TODO: do something else
723                 end
724                 PlaySound("igPlayerInviteDecline")
725                 del(info)
726                 return true
727         else
728                 
729                 local confirmText = option.confirmText
730                 --call confirm func/method
731                 if type(confirm) == "string" then
732                         if handler and handler[confirm] then
733                                 success, confirm = safecall(handler[confirm], handler, info, ...)
734                                 if success and type(confirm) == "string" then
735                                         confirmText = confirm
736                                         confirm = true
737                                 elseif not success then
738                                         confirm = false
739                                 end
740                         else
741                                 error(format("Method %s doesn't exist in handler for type confirm", confirm))
742                         end
743                 elseif type(confirm) == "function" then
744                         success, confirm = safecall(confirm, info, ...)
745                         if success and type(confirm) == "string" then
746                                 confirmText = confirm
747                                 confirm = true
748                         elseif not success then
749                                 confirm = false
750                         end
751                 end
752
753                 --confirm if needed
754                 if type(confirm) == "boolean" then
755                         if confirm then
756                                 if not confirmText then
757                                         local name, desc = option.name, option.desc
758                                         if type(name) == "function" then
759                                                 name = name(info)
760                                         end
761                                         if type(desc) == "function" then
762                                                 desc = desc(info)
763                                         end
764                                         confirmText = name
765                                         if desc then
766                                                 confirmText = confirmText.." - "..desc
767                                         end
768                                 end
769                                 
770                                 local iscustom = user.rootframe:GetUserData("iscustom")
771                                 local rootframe
772                                 
773                                 if iscustom then
774                                         rootframe = user.rootframe
775                                 end
776                                 local basepath = user.rootframe:GetUserData("basepath")
777                                 if type(func) == "string" then
778                                         if handler and handler[func] then
779                                                 confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...)
780                                         else
781                                                 error(format("Method %s doesn't exist in handler for type func", func))
782                                         end
783                                 elseif type(func) == "function" then
784                                         confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...)
785                                 end
786                                 --func will be called and info deleted when the confirm dialog is responded to
787                                 return
788                         end
789                 end
790
791                 --call the function
792                 if type(func) == "string" then
793                         if handler and handler[func] then
794                                 safecall(handler[func],handler, info, ...)
795                         else
796                                 error(format("Method %s doesn't exist in handler for type func", func))
797                         end
798                 elseif type(func) == "function" then
799                         safecall(func,info, ...)
800                 end
801
802
803
804                 local iscustom = user.rootframe:GetUserData("iscustom")
805                 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
806                 --full refresh of the frame, some controls dont cause this on all events
807                 if option.type == "color" then
808                         if event == "OnValueConfirmed" then
809                                 
810                                 if iscustom then
811                                         AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
812                                 else
813                                         AceConfigDialog:Open(user.appName, unpack(basepath))
814                                 end
815                         end
816                 elseif option.type == "range" then
817                         if event == "OnMouseUp" then
818                                 if iscustom then
819                                         AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
820                                 else
821                                         AceConfigDialog:Open(user.appName, unpack(basepath))
822                                 end
823                         end
824                 --multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed'
825                 elseif option.type == "multiselect" then
826                         user.valuechanged = true
827                 else
828                         if iscustom then
829                                 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
830                         else
831                                 AceConfigDialog:Open(user.appName, unpack(basepath))
832                         end
833                 end
834
835         end
836         del(info)
837 end
838
839 local function ActivateSlider(widget, event, value)
840         local option = widget:GetUserData("option")
841         local min, max, step = option.min or (not option.softMin and 0 or nil), option.max or (not option.softMax and 100 or nil), option.step
842         if min then
843                 if step then
844                         value = math_floor((value - min) / step + 0.5) * step + min
845                 end
846                 value = math_max(value, min)
847         end
848         if max then
849                 value = math_min(value, max)
850         end
851         ActivateControl(widget,event,value)
852 end
853
854 --called from a checkbox that is part of an internally created multiselect group
855 --this type is safe to refresh on activation of one control
856 local function ActivateMultiControl(widget, event, ...)
857         ActivateControl(widget, event, widget:GetUserData("value"), ...)
858         local user = widget:GetUserDataTable()
859         local iscustom = user.rootframe:GetUserData("iscustom")
860         local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
861         if iscustom then
862                 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
863         else
864                 AceConfigDialog:Open(user.appName, unpack(basepath))
865         end
866 end
867
868 local function MultiControlOnClosed(widget, event, ...)
869         local user = widget:GetUserDataTable()
870         if user.valuechanged then
871                 local iscustom = user.rootframe:GetUserData("iscustom")
872                 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
873                 if iscustom then
874                         AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
875                 else
876                         AceConfigDialog:Open(user.appName, unpack(basepath))
877                 end
878         end
879 end
880
881 local function FrameOnClose(widget, event)
882         local appName = widget:GetUserData("appName")
883         AceConfigDialog.OpenFrames[appName] = nil
884         gui:Release(widget)
885 end
886
887 local function CheckOptionHidden(option, options, path, appName)
888         --check for a specific boolean option
889         local hidden = pickfirstset(option.dialogHidden,option.guiHidden)
890         if hidden ~= nil then
891                 return hidden
892         end
893
894         return GetOptionsMemberValue("hidden", option, options, path, appName)
895 end
896
897 local function CheckOptionDisabled(option, options, path, appName)
898         --check for a specific boolean option
899         local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled)
900         if disabled ~= nil then
901                 return disabled
902         end
903
904         return GetOptionsMemberValue("disabled", option, options, path, appName)
905 end
906 --[[
907 local function BuildTabs(group, options, path, appName)
908         local tabs = new()
909         local text = new()
910         local keySort = new()
911         local opts = new()
912
913         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
914
915         for i = 1, #keySort do
916                 local k = keySort[i]
917                 local v = opts[k]
918                 if v.type == "group" then
919                         path[#path+1] = k
920                         local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
921                         local hidden = CheckOptionHidden(v, options, path, appName)
922                         if not inline and not hidden then
923                                 tinsert(tabs, k)
924                                 text[k] = GetOptionsMemberValue("name", v, options, path, appName)
925                         end
926                         path[#path] = nil
927                 end
928         end
929
930         del(keySort)
931         del(opts)
932
933         return tabs, text
934 end
935 ]]
936 local function BuildSelect(group, options, path, appName)
937         local groups = new()
938         local order = new()
939         local keySort = new()
940         local opts = new()
941
942         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
943
944         for i = 1, #keySort do
945                 local k = keySort[i]
946                 local v = opts[k]
947                 if v.type == "group" then
948                         path[#path+1] = k
949                         local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
950                         local hidden = CheckOptionHidden(v, options, path, appName)
951                         if not inline and not hidden then
952                                 groups[k] = GetOptionsMemberValue("name", v, options, path, appName)
953                                 tinsert(order, k)
954                         end
955                         path[#path] = nil
956                 end
957         end
958
959         del(opts)
960         del(keySort)
961
962         return groups, order
963 end
964
965 local function BuildSubGroups(group, tree, options, path, appName)
966         local keySort = new()
967         local opts = new()
968
969         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
970
971         for i = 1, #keySort do
972                 local k = keySort[i]
973                 local v = opts[k]
974                 if v.type == "group" then
975                         path[#path+1] = k
976                         local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
977                         local hidden = CheckOptionHidden(v, options, path, appName)
978                         if not inline and not hidden then
979                                 local entry = new()
980                                 entry.value = k
981                                 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
982                                 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
983                                 entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName)
984                                 entry.disabled = CheckOptionDisabled(v, options, path, appName)
985                                 if not tree.children then tree.children = new() end
986                                 tinsert(tree.children,entry)
987                                 if (v.childGroups or "tree") == "tree" then
988                                         BuildSubGroups(v,entry, options, path, appName)
989                                 end
990                         end
991                         path[#path] = nil
992                 end
993         end
994
995         del(keySort)
996         del(opts)
997 end
998
999 local function BuildGroups(group, options, path, appName, recurse)
1000         local tree = new()
1001         local keySort = new()
1002         local opts = new()
1003
1004         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1005
1006         for i = 1, #keySort do
1007                 local k = keySort[i]
1008                 local v = opts[k]
1009                 if v.type == "group" then
1010                         path[#path+1] = k
1011                         local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1012                         local hidden = CheckOptionHidden(v, options, path, appName)
1013                         if not inline and not hidden then
1014                                 local entry = new()
1015                                 entry.value = k
1016                                 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
1017                                 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
1018                                 entry.disabled = CheckOptionDisabled(v, options, path, appName)
1019                                 tinsert(tree,entry)
1020                                 if recurse and (v.childGroups or "tree") == "tree" then
1021                                         BuildSubGroups(v,entry, options, path, appName)
1022                                 end
1023                         end
1024                         path[#path] = nil
1025                 end
1026         end
1027         del(keySort)
1028         del(opts)
1029         return tree
1030 end
1031
1032 local function InjectInfo(control, options, option, path, rootframe, appName)
1033         local user = control:GetUserDataTable()
1034         for i = 1, #path do
1035                 user[i] = path[i]
1036         end
1037         user.rootframe = rootframe
1038         user.option = option
1039         user.options = options
1040         user.path = copy(path)
1041         user.appName = appName
1042         control:SetCallback("OnRelease", CleanUserData)
1043         control:SetCallback("OnLeave", OptionOnMouseLeave)
1044         control:SetCallback("OnEnter", OptionOnMouseOver)
1045 end
1046
1047
1048 --[[
1049         options - root of the options table being fed
1050         container - widget that controls will be placed in
1051         rootframe - Frame object the options are in
1052         path - table with the keys to get to the group being fed
1053 --]]
1054
1055 local function FeedOptions(appName, options,container,rootframe,path,group,inline)
1056         local keySort = new()
1057         local opts = new()
1058
1059         BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1060
1061         for i = 1, #keySort do
1062                 local k = keySort[i]
1063                 local v = opts[k]
1064                 tinsert(path, k)
1065                 local hidden = CheckOptionHidden(v, options, path, appName)
1066                 local name = GetOptionsMemberValue("name", v, options, path, appName)
1067                 if not hidden then
1068                         if v.type == "group" then
1069                                 if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
1070                                         --Inline group
1071                                         local GroupContainer
1072                                         if name and name ~= "" then
1073                                                 GroupContainer = gui:Create("InlineGroup")
1074                                                 GroupContainer:SetTitle(name or "")
1075                                         else
1076                                                 GroupContainer = gui:Create("SimpleGroup")
1077                                         end
1078                                         
1079                                         GroupContainer.width = "fill"
1080                                         GroupContainer:SetLayout("flow")
1081                                         container:AddChild(GroupContainer)
1082                                         FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
1083                                 end
1084                         else
1085                                 --Control to feed
1086                                 local control
1087                                 
1088                                 local name = GetOptionsMemberValue("name", v, options, path, appName)
1089                                 
1090                                 if v.type == "execute" then
1091                                         
1092                                         local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1093                                         local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1094                                         
1095                                         if type(image) == "string" then
1096                                                 control = gui:Create("Icon")
1097                                                 if not width then
1098                                                         width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1099                                                 end
1100                                                 if not height then
1101                                                         height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1102                                                 end
1103                                                 if type(imageCoords) == "table" then
1104                                                         control:SetImage(image, unpack(imageCoords))
1105                                                 else
1106                                                         control:SetImage(image)
1107                                                 end
1108                                                 if type(width) ~= "number" then
1109                                                         width = 32
1110                                                 end
1111                                                 if type(height) ~= "number" then
1112                                                         height = 32
1113                                                 end
1114                                                 control:SetImageSize(width, height)
1115                                                 control:SetLabel(name)
1116                                         else
1117                                                 control = gui:Create("Button")
1118                                                 control:SetText(name)
1119                                         end
1120                                         control:SetCallback("OnClick",ActivateControl)
1121
1122                                 elseif v.type == "input" then
1123                                         local controlType = v.dialogControl or v.control or (v.multiline and "MultiLineEditBox") or "EditBox"
1124                                         control = gui:Create(controlType)
1125                                         if not control then
1126                                                 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1127                                                 control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox")
1128                                         end
1129                                         
1130                                         if v.multiline and control.SetNumLines then
1131                                                 control:SetNumLines(tonumber(v.multiline) or 4)
1132                                         end
1133                                         control:SetLabel(name)
1134                                         control:SetCallback("OnEnterPressed",ActivateControl)
1135                                         local text = GetOptionsMemberValue("get",v, options, path, appName)
1136                                         if type(text) ~= "string" then
1137                                                 text = ""
1138                                         end
1139                                         control:SetText(text)
1140
1141                                 elseif v.type == "toggle" then
1142                                         control = gui:Create("CheckBox")
1143                                         control:SetLabel(name)
1144                                         control:SetTriState(v.tristate)
1145                                         local value = GetOptionsMemberValue("get",v, options, path, appName)
1146                                         control:SetValue(value)
1147                                         control:SetCallback("OnValueChanged",ActivateControl)
1148                                         
1149                                         if v.descStyle == "inline" then
1150                                                 local desc = GetOptionsMemberValue("desc", v, options, path, appName)
1151                                                 control:SetDescription(desc)
1152                                         end
1153                                         
1154                                         local image = GetOptionsMemberValue("image", v, options, path, appName)
1155                                         local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName)
1156                                         
1157                                         if type(image) == "string" then
1158                                                 if type(imageCoords) == "table" then
1159                                                         control:SetImage(image, unpack(imageCoords))
1160                                                 else
1161                                                         control:SetImage(image)
1162                                                 end
1163                                         end
1164                                 elseif v.type == "range" then
1165                                         control = gui:Create("Slider")
1166                                         control:SetLabel(name)
1167                                         control:SetSliderValues(v.softMin or v.min or 0, v.softMax or v.max or 100, v.bigStep or v.step or 0)
1168                                         control:SetIsPercent(v.isPercent)
1169                                         local value = GetOptionsMemberValue("get",v, options, path, appName)
1170                                         if type(value) ~= "number" then
1171                                                 value = 0
1172                                         end
1173                                         control:SetValue(value)
1174                                         control:SetCallback("OnValueChanged",ActivateSlider)
1175                                         control:SetCallback("OnMouseUp",ActivateSlider)
1176
1177                                 elseif v.type == "select" then
1178                                         local values = GetOptionsMemberValue("values", v, options, path, appName)
1179                                         if v.style == "radio" then
1180                                                 local disabled = CheckOptionDisabled(v, options, path, appName)
1181                                                 local width = GetOptionsMemberValue("width",v,options,path,appName)
1182                                                 control = gui:Create("InlineGroup")
1183                                                 control:SetLayout("Flow")
1184                                                 control:SetTitle(name)
1185                                                 control.width = "fill"
1186
1187                                                 control:PauseLayout()
1188                                                 local optionValue = GetOptionsMemberValue("get",v, options, path, appName)
1189                                                 local t = {}
1190                                                 for value, text in pairs(values) do
1191                                                         t[#t+1]=value
1192                                                 end
1193                                                 tsort(t)
1194                                                 for k, value in ipairs(t) do
1195                                                         local text = values[value]
1196                                                         local radio = gui:Create("CheckBox")
1197                                                         radio:SetLabel(text)
1198                                                         radio:SetUserData("value", value)
1199                                                         radio:SetUserData("text", text)
1200                                                         radio:SetDisabled(disabled)
1201                                                         radio:SetType("radio")
1202                                                         radio:SetValue(optionValue == value)
1203                                                         radio:SetCallback("OnValueChanged", ActivateMultiControl)
1204                                                         InjectInfo(radio, options, v, path, rootframe, appName)
1205                                                         control:AddChild(radio)
1206                                                         if width == "double" then
1207                                                                 radio:SetWidth(width_multiplier * 2)
1208                                                         elseif width == "half" then
1209                                                                 radio:SetWidth(width_multiplier / 2)
1210                                                         elseif width == "full" then
1211                                                                 radio.width = "fill"
1212                                                         else
1213                                                                 radio:SetWidth(width_multiplier)
1214                                                         end
1215                                                 end
1216                                                 control:ResumeLayout()
1217                                                 control:DoLayout()
1218                                         else
1219                                                 local controlType = v.dialogControl or v.control or "Dropdown"
1220                                                 control = gui:Create(controlType)
1221                                                 if not control then
1222                                                         geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1223                                                         control = gui:Create("Dropdown")
1224                                                 end
1225                                                 local itemType = v.itemControl
1226                                                 if itemType and not gui:GetWidgetVersion(itemType) then
1227                                                         geterrorhandler()(("Invalid Custom Item Type - %s"):format(tostring(itemType)))
1228                                                         itemType = nil
1229                                                 end
1230                                                 control:SetLabel(name)
1231                                                 control:SetList(values, nil, itemType)
1232                                                 local value = GetOptionsMemberValue("get",v, options, path, appName)
1233                                                 if not values[value] then
1234                                                         value = nil
1235                                                 end
1236                                                 control:SetValue(value)
1237                                                 control:SetCallback("OnValueChanged", ActivateControl)
1238                                         end
1239
1240                                 elseif v.type == "multiselect" then
1241                                         local values = GetOptionsMemberValue("values", v, options, path, appName)
1242                                         local disabled = CheckOptionDisabled(v, options, path, appName)
1243                                         
1244                                         local controlType = v.dialogControl or v.control
1245                                         
1246                                         local valuesort = new()
1247                                         if values then
1248                                                 for value, text in pairs(values) do
1249                                                         tinsert(valuesort, value)
1250                                                 end
1251                                         end
1252                                         tsort(valuesort)
1253                                         
1254                                         if controlType then
1255                                                 control = gui:Create(controlType)
1256                                                 if not control then
1257                                                         geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1258                                                 end
1259                                         end
1260                                         if control then
1261                                                 control:SetMultiselect(true)
1262                                                 control:SetLabel(name)
1263                                                 control:SetList(values)
1264                                                 control:SetDisabled(disabled)
1265                                                 control:SetCallback("OnValueChanged",ActivateControl)
1266                                                 control:SetCallback("OnClosed", MultiControlOnClosed)
1267                                                 local width = GetOptionsMemberValue("width",v,options,path,appName)
1268                                                 if width == "double" then
1269                                                         control:SetWidth(width_multiplier * 2)
1270                                                 elseif width == "half" then
1271                                                         control:SetWidth(width_multiplier / 2)
1272                                                 elseif width == "full" then
1273                                                         control.width = "fill"
1274                                                 else
1275                                                         control:SetWidth(width_multiplier)
1276                                                 end
1277                                                 --check:SetTriState(v.tristate)
1278                                                 for i = 1, #valuesort do
1279                                                         local key = valuesort[i]
1280                                                         local value = GetOptionsMemberValue("get",v, options, path, appName, key)
1281                                                         control:SetItemValue(key,value)
1282                                                 end
1283                                         else
1284                                                 control = gui:Create("InlineGroup")
1285                                                 control:SetLayout("Flow")
1286                                                 control:SetTitle(name)
1287                                                 control.width = "fill"
1288
1289                                                 control:PauseLayout()
1290                                                 local width = GetOptionsMemberValue("width",v,options,path,appName)
1291                                                 for i = 1, #valuesort do
1292                                                         local value = valuesort[i]
1293                                                         local text = values[value]
1294                                                         local check = gui:Create("CheckBox")
1295                                                         check:SetLabel(text)
1296                                                         check:SetUserData("value", value)
1297                                                         check:SetUserData("text", text)
1298                                                         check:SetDisabled(disabled)
1299                                                         check:SetTriState(v.tristate)
1300                                                         check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
1301                                                         check:SetCallback("OnValueChanged",ActivateMultiControl)
1302                                                         InjectInfo(check, options, v, path, rootframe, appName)
1303                                                         control:AddChild(check)
1304                                                         if width == "double" then
1305                                                                 check:SetWidth(width_multiplier * 2)
1306                                                         elseif width == "half" then
1307                                                                 check:SetWidth(width_multiplier / 2)
1308                                                         elseif width == "full" then
1309                                                                 check.width = "fill"
1310                                                         else
1311                                                                 check:SetWidth(width_multiplier)
1312                                                         end
1313                                                 end
1314                                                 control:ResumeLayout()
1315                                                 control:DoLayout()
1316
1317                                                 
1318                                         end
1319                                         
1320                                         del(valuesort)
1321
1322                                 elseif v.type == "color" then
1323                                         control = gui:Create("ColorPicker")
1324                                         control:SetLabel(name)
1325                                         control:SetHasAlpha(GetOptionsMemberValue("hasAlpha",v, options, path, appName))
1326                                         control:SetColor(GetOptionsMemberValue("get",v, options, path, appName))
1327                                         control:SetCallback("OnValueChanged",ActivateControl)
1328                                         control:SetCallback("OnValueConfirmed",ActivateControl)
1329
1330                                 elseif v.type == "keybinding" then
1331                                         control = gui:Create("Keybinding")
1332                                         control:SetLabel(name)
1333                                         control:SetKey(GetOptionsMemberValue("get",v, options, path, appName))
1334                                         control:SetCallback("OnKeyChanged",ActivateControl)
1335
1336                                 elseif v.type == "header" then
1337                                         control = gui:Create("Heading")
1338                                         control:SetText(name)
1339                                         control.width = "fill"
1340
1341                                 elseif v.type == "description" then
1342                                         control = gui:Create("Label")
1343                                         control:SetText(name)
1344                                         
1345                                         local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName)
1346                                         if fontSize == "medium" then
1347                                                 control:SetFontObject(GameFontHighlight)
1348                                         elseif fontSize == "large" then
1349                                                 control:SetFontObject(GameFontHighlightLarge)
1350                                         else -- small or invalid
1351                                                 control:SetFontObject(GameFontHighlightSmall)
1352                                         end
1353                                         
1354                                         local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1355                                         local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1356                                         
1357                                         if type(image) == "string" then
1358                                                 if not width then
1359                                                         width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1360                                                 end
1361                                                 if not height then
1362                                                         height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1363                                                 end
1364                                                 if type(imageCoords) == "table" then
1365                                                         control:SetImage(image, unpack(imageCoords))
1366                                                 else
1367                                                         control:SetImage(image)
1368                                                 end
1369                                                 if type(width) ~= "number" then
1370                                                         width = 32
1371                                                 end
1372                                                 if type(height) ~= "number" then
1373                                                         height = 32
1374                                                 end
1375                                                 control:SetImageSize(width, height)
1376                                         end
1377                                         local width = GetOptionsMemberValue("width",v,options,path,appName)
1378                                         control.width = not width and "fill"
1379                                 end
1380
1381                                 --Common Init
1382                                 if control then
1383                                         if control.width ~= "fill" then
1384                                                 local width = GetOptionsMemberValue("width",v,options,path,appName)
1385                                                 if width == "double" then
1386                                                         control:SetWidth(width_multiplier * 2)
1387                                                 elseif width == "half" then
1388                                                         control:SetWidth(width_multiplier / 2)
1389                                                 elseif width == "full" then
1390                                                         control.width = "fill"
1391                                                 else
1392                                                         control:SetWidth(width_multiplier)
1393                                                 end
1394                                         end
1395                                         if control.SetDisabled then
1396                                                 local disabled = CheckOptionDisabled(v, options, path, appName)
1397                                                 control:SetDisabled(disabled)
1398                                         end
1399
1400                                         InjectInfo(control, options, v, path, rootframe, appName)
1401                                         container:AddChild(control)
1402                                 end
1403                                 
1404                         end
1405                 end
1406                 tremove(path)
1407         end
1408         container:ResumeLayout()
1409         container:DoLayout()
1410         del(keySort)
1411         del(opts)
1412 end
1413
1414 local function BuildPath(path, ...)
1415         for i = 1, select("#",...)  do
1416                 tinsert(path, (select(i,...)))
1417         end
1418 end
1419
1420
1421 local function TreeOnButtonEnter(widget, event, uniquevalue, button)
1422         local user = widget:GetUserDataTable()
1423         if not user then return end
1424         local options = user.options
1425         local option = user.option
1426         local path = user.path
1427         local appName = user.appName
1428         
1429         local feedpath = new()
1430         for i = 1, #path do
1431                 feedpath[i] = path[i]
1432         end
1433
1434         BuildPath(feedpath, ("\001"):split(uniquevalue))
1435         local group = options
1436         for i = 1, #feedpath do
1437                 if not group then return end
1438                 group = GetSubOption(group, feedpath[i])
1439         end
1440
1441         local name = GetOptionsMemberValue("name", group, options, feedpath, appName)
1442         local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName)
1443         
1444         GameTooltip:SetOwner(button, "ANCHOR_NONE")
1445         if widget.type == "TabGroup" then
1446                 GameTooltip:SetPoint("BOTTOM",button,"TOP")
1447         else
1448                 GameTooltip:SetPoint("LEFT",button,"RIGHT")
1449         end
1450
1451         GameTooltip:SetText(name, 1, .82, 0, 1)
1452         
1453         if type(desc) == "string" then
1454                 GameTooltip:AddLine(desc, 1, 1, 1, 1)
1455         end
1456         
1457         GameTooltip:Show()
1458 end
1459
1460 local function TreeOnButtonLeave(widget, event, value, button)
1461         GameTooltip:Hide()
1462 end
1463
1464
1465 local function GroupExists(appName, options, path, uniquevalue)
1466         if not uniquevalue then return false end
1467         
1468         local feedpath = new()
1469         local temppath = new()
1470         for i = 1, #path do
1471                 feedpath[i] = path[i]
1472         end
1473         
1474         BuildPath(feedpath, ("\001"):split(uniquevalue))
1475         
1476         local group = options
1477         for i = 1, #feedpath do
1478                 local v = feedpath[i]
1479                 temppath[i] = v
1480                 group = GetSubOption(group, v)
1481                 
1482                 if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then 
1483                         del(feedpath)
1484                         del(temppath)
1485                         return false 
1486                 end
1487         end
1488         del(feedpath)
1489         del(temppath)
1490         return true
1491 end
1492
1493 local function GroupSelected(widget, event, uniquevalue)
1494
1495         local user = widget:GetUserDataTable()
1496
1497         local options = user.options
1498         local option = user.option
1499         local path = user.path
1500         local rootframe = user.rootframe
1501
1502         local feedpath = new()
1503         for i = 1, #path do
1504                 feedpath[i] = path[i]
1505         end
1506
1507         BuildPath(feedpath, ("\001"):split(uniquevalue))
1508         local group = options
1509         for i = 1, #feedpath do
1510                 group = GetSubOption(group, feedpath[i])
1511         end
1512         widget:ReleaseChildren()
1513         AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath)
1514
1515         del(feedpath)
1516 end
1517
1518
1519
1520 --[[
1521 -- INTERNAL --
1522 This function will feed one group, and any inline child groups into the given container
1523 Select Groups will only have the selection control (tree, tabs, dropdown) fed in
1524 and have a group selected, this event will trigger the feeding of child groups
1525
1526 Rules:
1527         If the group is Inline, FeedOptions
1528         If the group has no child groups, FeedOptions
1529
1530         If the group is a tab or select group, FeedOptions then add the Group Control
1531         If the group is a tree group FeedOptions then
1532                 its parent isnt a tree group:  then add the tree control containing this and all child tree groups
1533                 if its parent is a tree group, its already a node on a tree
1534 --]]
1535
1536 function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot)
1537         local group = options
1538         --follow the path to get to the curent group
1539         local inline
1540         local grouptype, parenttype = options.childGroups, "none"
1541
1542
1543         for i = 1, #path do
1544                 local v = path[i]
1545                 group = GetSubOption(group, v)
1546                 inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1547                 parenttype = grouptype
1548                 grouptype = group.childGroups
1549         end
1550
1551         if not parenttype then
1552                 parenttype = "tree"
1553         end
1554
1555         --check if the group has child groups
1556         local hasChildGroups
1557         for k, v in pairs(group.args) do
1558                 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1559                         hasChildGroups = true
1560                 end
1561         end
1562         if group.plugins then
1563                 for plugin, t in pairs(group.plugins) do
1564                         for k, v in pairs(t) do
1565                                 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1566                                         hasChildGroups = true
1567                                 end
1568                         end
1569                 end
1570         end
1571
1572         container:SetLayout("flow")
1573         local scroll
1574
1575         --Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on
1576         if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and (parenttype == "tree" and not isRoot)) then
1577                 if container.type ~= "InlineGroup" and container.type ~= "SimpleGroup" then
1578                         scroll = gui:Create("ScrollFrame")
1579                         scroll:SetLayout("flow")
1580                         scroll.width = "fill"
1581                         scroll.height = "fill"
1582                         container:SetLayout("fill")
1583                         container:AddChild(scroll)
1584                         container = scroll
1585                 end
1586         end
1587
1588         FeedOptions(appName,options,container,rootframe,path,group,nil)
1589
1590         if scroll then
1591                 container:PerformLayout()
1592                 local status = self:GetStatusTable(appName, path)
1593                 if not status.scroll then
1594                         status.scroll = {}
1595                 end
1596                 scroll:SetStatusTable(status.scroll)
1597         end
1598
1599         if hasChildGroups and not inline then
1600                 local name = GetOptionsMemberValue("name", group, options, path, appName)
1601                 if grouptype == "tab" then
1602
1603                         local tab = gui:Create("TabGroup")
1604                         InjectInfo(tab, options, group, path, rootframe, appName)
1605                         tab:SetCallback("OnGroupSelected", GroupSelected)
1606                         tab:SetCallback("OnTabEnter", TreeOnButtonEnter)
1607                         tab:SetCallback("OnTabLeave", TreeOnButtonLeave)
1608                         
1609                         local status = AceConfigDialog:GetStatusTable(appName, path)
1610                         if not status.groups then
1611                                 status.groups = {}
1612                         end
1613                         tab:SetStatusTable(status.groups)
1614                         tab.width = "fill"
1615                         tab.height = "fill"
1616
1617                         local tabs = BuildGroups(group, options, path, appName)
1618                         tab:SetTabs(tabs)
1619                         tab:SetUserData("tablist", tabs)
1620
1621                         for i = 1, #tabs do
1622                                 local entry = tabs[i]
1623                                 if not entry.disabled then
1624                                         tab:SelectTab((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1625                                         break
1626                                 end
1627                         end
1628                         
1629                         container:AddChild(tab)
1630
1631                 elseif grouptype == "select" then
1632
1633                         local select = gui:Create("DropdownGroup")
1634                         select:SetTitle(name)
1635                         InjectInfo(select, options, group, path, rootframe, appName)
1636                         select:SetCallback("OnGroupSelected", GroupSelected)
1637                         local status = AceConfigDialog:GetStatusTable(appName, path)
1638                         if not status.groups then
1639                                 status.groups = {}
1640                         end
1641                         select:SetStatusTable(status.groups)
1642                         local grouplist, orderlist = BuildSelect(group, options, path, appName)
1643                         select:SetGroupList(grouplist, orderlist)
1644                         select:SetUserData("grouplist", grouplist)
1645                         select:SetUserData("orderlist", orderlist)
1646
1647                         local firstgroup = orderlist[1]
1648                         if firstgroup then
1649                                 select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup)
1650                         end
1651                         
1652                         select.width = "fill"
1653                         select.height = "fill"
1654
1655                         container:AddChild(select)
1656
1657                 --assume tree group by default
1658                 --if parenttype is tree then this group is already a node on that tree
1659                 elseif (parenttype ~= "tree") or isRoot then
1660                         local tree = gui:Create("TreeGroup")
1661                         InjectInfo(tree, options, group, path, rootframe, appName)
1662                         tree:EnableButtonTooltips(false)
1663                         
1664                         tree.width = "fill"
1665                         tree.height = "fill"
1666
1667                         tree:SetCallback("OnGroupSelected", GroupSelected)
1668                         tree:SetCallback("OnButtonEnter", TreeOnButtonEnter)
1669                         tree:SetCallback("OnButtonLeave", TreeOnButtonLeave)
1670                         
1671                         local status = AceConfigDialog:GetStatusTable(appName, path)
1672                         if not status.groups then
1673                                 status.groups = {}
1674                         end
1675                         local treedefinition = BuildGroups(group, options, path, appName, true)
1676                         tree:SetStatusTable(status.groups)
1677
1678                         tree:SetTree(treedefinition)
1679                         tree:SetUserData("tree",treedefinition)
1680
1681                         for i = 1, #treedefinition do
1682                                 local entry = treedefinition[i]
1683                                 if not entry.disabled then
1684                                         tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1685                                         break
1686                                 end
1687                         end
1688
1689                         container:AddChild(tree)
1690                 end
1691         end
1692 end
1693
1694 local old_CloseSpecialWindows
1695
1696
1697 local function RefreshOnUpdate(this)
1698         for appName in pairs(this.closing) do
1699                 if AceConfigDialog.OpenFrames[appName] then
1700                         AceConfigDialog.OpenFrames[appName]:Hide()
1701                 end
1702                 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1703                         for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1704                                 if not widget:IsVisible() then
1705                                         widget:ReleaseChildren()
1706                                 end
1707                         end
1708                 end
1709                 this.closing[appName] = nil
1710         end
1711         
1712         if this.closeAll then
1713                 for k, v in pairs(AceConfigDialog.OpenFrames) do
1714                         if not this.closeAllOverride[k] then
1715                                 v:Hide()
1716                         end
1717                 end
1718                 this.closeAll = nil
1719                 wipe(this.closeAllOverride)
1720         end
1721         
1722         for appName in pairs(this.apps) do
1723                 if AceConfigDialog.OpenFrames[appName] then
1724                         local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable()
1725                         AceConfigDialog:Open(appName, unpack(user.basepath or emptyTbl))
1726                 end
1727                 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1728                         for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1729                                 local user = widget:GetUserDataTable()
1730                                 if widget:IsVisible() then
1731                                         AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(user.basepath or emptyTbl))
1732                                 end
1733                         end
1734                 end
1735                 this.apps[appName] = nil
1736         end
1737         this:SetScript("OnUpdate", nil)
1738 end
1739
1740 -- Upgrade the OnUpdate script as well, if needed.
1741 if AceConfigDialog.frame:GetScript("OnUpdate") then
1742         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1743 end
1744
1745 --- Close all open options windows
1746 function AceConfigDialog:CloseAll()
1747         AceConfigDialog.frame.closeAll = true
1748         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1749         if next(self.OpenFrames) then
1750                 return true
1751         end
1752 end
1753
1754 --- Close a specific options window.
1755 -- @param appName The application name as given to `:RegisterOptionsTable()`
1756 function AceConfigDialog:Close(appName)
1757         if self.OpenFrames[appName] then
1758                 AceConfigDialog.frame.closing[appName] = true
1759                 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1760                 return true
1761         end
1762 end
1763
1764 -- Internal -- Called by AceConfigRegistry
1765 function AceConfigDialog:ConfigTableChanged(event, appName)
1766         AceConfigDialog.frame.apps[appName] = true
1767         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1768 end
1769
1770 reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged")
1771
1772 --- Sets the default size of the options window for a specific application.
1773 -- @param appName The application name as given to `:RegisterOptionsTable()`
1774 -- @param width The default width
1775 -- @param height The default height
1776 function AceConfigDialog:SetDefaultSize(appName, width, height)
1777         local status = AceConfigDialog:GetStatusTable(appName)
1778         if type(width) == "number" and type(height) == "number" then
1779                 status.width = width
1780                 status.height = height
1781         end
1782 end
1783
1784 --- Open an option window at the specified path (if any).
1785 -- This function can optionally feed the group into a pre-created container
1786 -- instead of creating a new container frame.
1787 -- @paramsig appName [, container][, ...]
1788 -- @param appName The application name as given to `:RegisterOptionsTable()`
1789 -- @param container An optional container frame to feed the options into
1790 -- @param ... The path to open after creating the options window (see `:SelectGroup` for details)
1791 function AceConfigDialog:Open(appName, container, ...)
1792         if not old_CloseSpecialWindows then
1793                 old_CloseSpecialWindows = CloseSpecialWindows
1794                 CloseSpecialWindows = function()
1795                         local found = old_CloseSpecialWindows()
1796                         return self:CloseAll() or found
1797                 end
1798         end
1799         local app = reg:GetOptionsTable(appName)
1800         if not app then
1801                 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
1802         end
1803         local options = app("dialog", MAJOR)
1804
1805         local f
1806         
1807         local path = new()
1808         local name = GetOptionsMemberValue("name", options, options, path, appName)
1809         
1810         --If an optional path is specified add it to the path table before feeding the options
1811         --as container is optional as well it may contain the first element of the path
1812         if type(container) == "string" then
1813                 tinsert(path, container)
1814                 container = nil
1815         end
1816         for n = 1, select("#",...) do
1817                 tinsert(path, (select(n, ...)))
1818         end
1819         
1820         --if a container is given feed into that
1821         if container then
1822                 f = container
1823                 f:ReleaseChildren()
1824                 f:SetUserData("appName", appName)
1825                 f:SetUserData("iscustom", true)
1826                 if #path > 0 then
1827                         f:SetUserData("basepath", copy(path))
1828                 end
1829                 local status = AceConfigDialog:GetStatusTable(appName)
1830                 if not status.width then
1831                         status.width =  700
1832                 end
1833                 if not status.height then
1834                         status.height = 500
1835                 end
1836                 if f.SetStatusTable then
1837                         f:SetStatusTable(status)
1838                 end
1839                 if f.SetTitle then
1840                         f:SetTitle(name or "")
1841                 end
1842         else
1843                 if not self.OpenFrames[appName] then
1844                         f = gui:Create("Frame")
1845                         self.OpenFrames[appName] = f
1846                 else
1847                         f = self.OpenFrames[appName]
1848                 end
1849                 f:ReleaseChildren()
1850                 f:SetCallback("OnClose", FrameOnClose)
1851                 f:SetUserData("appName", appName)
1852                 if #path > 0 then
1853                         f:SetUserData("basepath", copy(path))
1854                 end
1855                 f:SetTitle(name or "")
1856                 local status = AceConfigDialog:GetStatusTable(appName)
1857                 f:SetStatusTable(status)
1858         end
1859
1860         self:FeedGroup(appName,options,f,f,path,true)
1861         if f.Show then
1862                 f:Show()
1863         end
1864         del(path)
1865
1866         if AceConfigDialog.frame.closeAll then
1867                 -- close all is set, but thats not good, since we're just opening here, so force it
1868                 AceConfigDialog.frame.closeAllOverride[appName] = true
1869         end
1870 end
1871
1872 -- convert pre-39 BlizOptions structure to the new format
1873 if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then
1874         local old = AceConfigDialog.BlizOptions
1875         local new = {}
1876         for key, widget in pairs(old) do
1877                 local appName = widget:GetUserData("appName")
1878                 if not new[appName] then new[appName] = {} end
1879                 new[appName][key] = widget
1880         end
1881         AceConfigDialog.BlizOptions = new
1882 else
1883         AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {}
1884 end
1885
1886 local function FeedToBlizPanel(widget, event)
1887         local path = widget:GetUserData("path")
1888         AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl))
1889 end
1890
1891 local function ClearBlizPanel(widget, event)
1892         local appName = widget:GetUserData("appName")
1893         AceConfigDialog.frame.closing[appName] = true
1894         AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1895 end
1896
1897 --- Add an option table into the Blizzard Interface Options panel.
1898 -- You can optionally supply a descriptive name to use and a parent frame to use,
1899 -- as well as a path in the options table.\\
1900 -- If no name is specified, the appName will be used instead.
1901 --
1902 -- If you specify a proper `parent` (by name), the interface options will generate a
1903 -- tree layout. Note that only one level of children is supported, so the parent always
1904 -- has to be a head-level note.
1905 --
1906 -- This function returns a reference to the container frame registered with the Interface
1907 -- Options. You can use this reference to open the options with the API function
1908 -- `InterfaceOptionsFrame_OpenToCategory`.
1909 -- @param appName The application name as given to `:RegisterOptionsTable()`
1910 -- @param name A descriptive name to display in the options tree (defaults to appName)
1911 -- @param parent The parent to use in the interface options tree.
1912 -- @param ... The path in the options table to feed into the interface options panel.
1913 -- @return The reference to the frame registered into the Interface Options. 
1914 function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
1915         local BlizOptions = AceConfigDialog.BlizOptions
1916         
1917         local key = appName
1918         for n = 1, select("#", ...) do
1919                 key = key.."\001"..select(n, ...)
1920         end
1921         
1922         if not BlizOptions[appName] then
1923                 BlizOptions[appName] = {}
1924         end
1925         
1926         if not BlizOptions[appName][key] then
1927                 local group = gui:Create("BlizOptionsGroup")
1928                 BlizOptions[appName][key] = group
1929                 group:SetName(name or appName, parent)
1930
1931                 group:SetTitle(name or appName)
1932                 group:SetUserData("appName", appName)
1933                 if select("#", ...) > 0 then
1934                         local path = {}
1935                         for n = 1, select("#",...) do
1936                                 tinsert(path, (select(n, ...)))
1937                         end
1938                         group:SetUserData("path", path)
1939                 end
1940                 group:SetCallback("OnShow", FeedToBlizPanel)
1941                 group:SetCallback("OnHide", ClearBlizPanel)
1942                 InterfaceOptions_AddCategory(group.frame)
1943                 return group.frame
1944         else
1945                 error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2)
1946         end
1947 end