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

First commit Hot Corners standalone.
author tercio
date Fri, 08 Aug 2014 12:35:17 -0300
parents
children c31ee4251181
comparison
equal deleted inserted replaced
-1:000000000000 0:fc346da3afd9
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