comparison Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua @ 0:98c6f55e6619

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