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

Reasonably functional now. Cleaning up some stuff which might have to be reverted.
author Zerotorescue
date Thu, 07 Oct 2010 17:17:43 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c6ff7ba0e8f6
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 958 2010-07-03 10:22:29Z nevcairiel $
5
6 local LibStub = LibStub
7 local MAJOR, MINOR = "AceConfigDialog-3.0", 49
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 for k in pairs(t) do
129 t[k] = nil
130 end
131 pool[t] = true
132 end
133 -- function cached()
134 -- local n = 0
135 -- for k in pairs(pool) do
136 -- n = n + 1
137 -- end
138 -- return n
139 -- end
140 end
141
142 -- picks the first non-nil value and returns it
143 local function pickfirstset(...)
144 for i=1,select("#",...) do
145 if select(i,...)~=nil then
146 return select(i,...)
147 end
148 end
149 end
150
151 --gets an option from a given group, checking plugins
152 local function GetSubOption(group, key)
153 if group.plugins then
154 for plugin, t in pairs(group.plugins) do
155 if t[key] then
156 return t[key]
157 end
158 end
159 end
160
161 return group.args[key]
162 end
163
164 --Option member type definitions, used to decide how to access it
165
166 --Is the member Inherited from parent options
167 local isInherited = {
168 set = true,
169 get = true,
170 func = true,
171 confirm = true,
172 validate = true,
173 disabled = true,
174 hidden = true
175 }
176
177 --Does a string type mean a literal value, instead of the default of a method of the handler
178 local stringIsLiteral = {
179 name = true,
180 desc = true,
181 icon = true,
182 usage = true,
183 width = true,
184 image = true,
185 fontSize = true,
186 }
187
188 --Is Never a function or method
189 local allIsLiteral = {
190 type = true,
191 descStyle = true,
192 imageWidth = true,
193 imageHeight = true,
194 }
195
196 --gets the value for a member that could be a function
197 --function refs are called with an info arg
198 --every other type is returned
199 local function GetOptionsMemberValue(membername, option, options, path, appName, ...)
200 --get definition for the member
201 local inherits = isInherited[membername]
202
203
204 --get the member of the option, traversing the tree if it can be inherited
205 local member
206
207 if inherits then
208 local group = options
209 if group[membername] ~= nil then
210 member = group[membername]
211 end
212 for i = 1, #path do
213 group = GetSubOption(group, path[i])
214 if group[membername] ~= nil then
215 member = group[membername]
216 end
217 end
218 else
219 member = option[membername]
220 end
221
222 --check if we need to call a functon, or if we have a literal value
223 if ( not allIsLiteral[membername] ) and ( type(member) == "function" or ((not stringIsLiteral[membername]) and type(member) == "string") ) then
224 --We have a function to call
225 local info = new()
226 --traverse the options table, picking up the handler and filling the info with the path
227 local handler
228 local group = options
229 handler = group.handler or handler
230
231 for i = 1, #path do
232 group = GetSubOption(group, path[i])
233 info[i] = path[i]
234 handler = group.handler or handler
235 end
236
237 info.options = options
238 info.appName = appName
239 info[0] = appName
240 info.arg = option.arg
241 info.handler = handler
242 info.option = option
243 info.type = option.type
244 info.uiType = "dialog"
245 info.uiName = MAJOR
246
247 local a, b, c ,d
248 --using 4 returns for the get of a color type, increase if a type needs more
249 if type(member) == "function" then
250 --Call the function
251 a,b,c,d = member(info, ...)
252 else
253 --Call the method
254 if handler and handler[member] then
255 a,b,c,d = handler[member](handler, info, ...)
256 else
257 error(format("Method %s doesn't exist in handler for type %s", member, membername))
258 end
259 end
260 del(info)
261 return a,b,c,d
262 else
263 --The value isnt a function to call, return it
264 return member
265 end
266 end
267
268 --[[calls an options function that could be inherited, method name or function ref
269 local function CallOptionsFunction(funcname ,option, options, path, appName, ...)
270 local info = new()
271
272 local func
273 local group = options
274 local handler
275
276 --build the info table containing the path
277 -- pick up functions while traversing the tree
278 if group[funcname] ~= nil then
279 func = group[funcname]
280 end
281 handler = group.handler or handler
282
283 for i, v in ipairs(path) do
284 group = GetSubOption(group, v)
285 info[i] = v
286 if group[funcname] ~= nil then
287 func = group[funcname]
288 end
289 handler = group.handler or handler
290 end
291
292 info.options = options
293 info[0] = appName
294 info.arg = option.arg
295
296 local a, b, c ,d
297 if type(func) == "string" then
298 if handler and handler[func] then
299 a,b,c,d = handler[func](handler, info, ...)
300 else
301 error(string.format("Method %s doesn't exist in handler for type func", func))
302 end
303 elseif type(func) == "function" then
304 a,b,c,d = func(info, ...)
305 end
306 del(info)
307 return a,b,c,d
308 end
309 --]]
310
311 --tables to hold orders and names for options being sorted, will be created with new()
312 --prevents needing to call functions repeatedly while sorting
313 local tempOrders
314 local tempNames
315
316 local function compareOptions(a,b)
317 if not a then
318 return true
319 end
320 if not b then
321 return false
322 end
323 local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100
324 if OrderA == OrderB then
325 local NameA = (type(tempNames[a] == "string") and tempNames[a]) or ""
326 local NameB = (type(tempNames[b] == "string") and tempNames[b]) or ""
327 return NameA:upper() < NameB:upper()
328 end
329 if OrderA < 0 then
330 if OrderB > 0 then
331 return false
332 end
333 else
334 if OrderB < 0 then
335 return true
336 end
337 end
338 return OrderA < OrderB
339 end
340
341
342
343 --builds 2 tables out of an options group
344 -- keySort, sorted keys
345 -- opts, combined options from .plugins and args
346 local function BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
347 tempOrders = new()
348 tempNames = new()
349
350 if group.plugins then
351 for plugin, t in pairs(group.plugins) do
352 for k, v in pairs(t) do
353 if not opts[k] then
354 tinsert(keySort, k)
355 opts[k] = v
356
357 path[#path+1] = k
358 tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
359 tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
360 path[#path] = nil
361 end
362 end
363 end
364 end
365
366 for k, v in pairs(group.args) do
367 if not opts[k] then
368 tinsert(keySort, k)
369 opts[k] = v
370
371 path[#path+1] = k
372 tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
373 tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
374 path[#path] = nil
375 end
376 end
377
378 tsort(keySort, compareOptions)
379
380 del(tempOrders)
381 del(tempNames)
382 end
383
384 local function DelTree(tree)
385 if tree.children then
386 local childs = tree.children
387 for i = 1, #childs do
388 DelTree(childs[i])
389 del(childs[i])
390 end
391 del(childs)
392 end
393 end
394
395 local function CleanUserData(widget, event)
396
397 local user = widget:GetUserDataTable()
398
399 if user.path then
400 del(user.path)
401 end
402
403 if widget.type == "TreeGroup" then
404 local tree = user.tree
405 widget:SetTree(nil)
406 if tree then
407 for i = 1, #tree do
408 DelTree(tree[i])
409 del(tree[i])
410 end
411 del(tree)
412 end
413 end
414
415 if widget.type == "TabGroup" then
416 widget:SetTabs(nil)
417 if user.tablist then
418 del(user.tablist)
419 end
420 end
421
422 if widget.type == "DropdownGroup" then
423 widget:SetGroupList(nil)
424 if user.grouplist then
425 del(user.grouplist)
426 end
427 end
428 end
429
430 -- - Gets a status table for the given appname and options path.
431 -- @param appName The application name as given to `:RegisterOptionsTable()`
432 -- @param path The path to the options (a table with all group keys)
433 -- @return
434 function AceConfigDialog:GetStatusTable(appName, path)
435 local status = self.Status
436
437 if not status[appName] then
438 status[appName] = {}
439 status[appName].status = {}
440 status[appName].children = {}
441 end
442
443 status = status[appName]
444
445 if path then
446 for i = 1, #path do
447 local v = path[i]
448 if not status.children[v] then
449 status.children[v] = {}
450 status.children[v].status = {}
451 status.children[v].children = {}
452 end
453 status = status.children[v]
454 end
455 end
456
457 return status.status
458 end
459
460 --- Selects the specified path in the options window.
461 -- The path specified has to match the keys of the groups in the table.
462 -- @param appName The application name as given to `:RegisterOptionsTable()`
463 -- @param ... The path to the key that should be selected
464 function AceConfigDialog:SelectGroup(appName, ...)
465 local path = new()
466
467
468 local app = reg:GetOptionsTable(appName)
469 if not app then
470 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
471 end
472 local options = app("dialog", MAJOR)
473 local group = options
474 local status = self:GetStatusTable(appName, path)
475 if not status.groups then
476 status.groups = {}
477 end
478 status = status.groups
479 local treevalue
480 local treestatus
481
482 for n = 1, select("#",...) do
483 local key = select(n, ...)
484
485 if group.childGroups == "tab" or group.childGroups == "select" then
486 --if this is a tab or select group, select the group
487 status.selected = key
488 --children of this group are no longer extra levels of a tree
489 treevalue = nil
490 else
491 --tree group by default
492 if treevalue then
493 --this is an extra level of a tree group, build a uniquevalue for it
494 treevalue = treevalue.."\001"..key
495 else
496 --this is the top level of a tree group, the uniquevalue is the same as the key
497 treevalue = key
498 if not status.groups then
499 status.groups = {}
500 end
501 --save this trees status table for any extra levels or groups
502 treestatus = status
503 end
504 --make sure that the tree entry is open, and select it.
505 --the selected group will be overwritten if a child is the final target but still needs to be open
506 treestatus.selected = treevalue
507 treestatus.groups[treevalue] = true
508
509 end
510
511 --move to the next group in the path
512 group = GetSubOption(group, key)
513 if not group then
514 break
515 end
516 tinsert(path, key)
517 status = self:GetStatusTable(appName, path)
518 if not status.groups then
519 status.groups = {}
520 end
521 status = status.groups
522 end
523
524 del(path)
525 reg:NotifyChange(appName)
526 end
527
528 local function OptionOnMouseOver(widget, event)
529 --show a tooltip/set the status bar to the desc text
530 local user = widget:GetUserDataTable()
531 local opt = user.option
532 local options = user.options
533 local path = user.path
534 local appName = user.appName
535
536 GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT")
537 local name = GetOptionsMemberValue("name", opt, options, path, appName)
538 local desc = GetOptionsMemberValue("desc", opt, options, path, appName)
539 local usage = GetOptionsMemberValue("usage", opt, options, path, appName)
540 local descStyle = opt.descStyle
541
542 if descStyle and descStyle ~= "tooltip" then return end
543
544 GameTooltip:SetText(name, 1, .82, 0, 1)
545
546 if opt.type == "multiselect" then
547 GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1)
548 end
549 if type(desc) == "string" then
550 GameTooltip:AddLine(desc, 1, 1, 1, 1)
551 end
552 if type(usage) == "string" then
553 GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
554 end
555
556 GameTooltip:Show()
557 end
558
559 local function OptionOnMouseLeave(widget, event)
560 GameTooltip:Hide()
561 end
562
563 local function GetFuncName(option)
564 local type = option.type
565 if type == "execute" then
566 return "func"
567 else
568 return "set"
569 end
570 end
571 local function confirmPopup(appName, rootframe, basepath, info, message, func, ...)
572 if not StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] then
573 StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] = {}
574 end
575 local t = StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"]
576 for k in pairs(t) do
577 t[k] = nil
578 end
579 t.text = message
580 t.button1 = ACCEPT
581 t.button2 = CANCEL
582 local dialog, oldstrata
583 t.OnAccept = function()
584 safecall(func, unpack(t))
585 if dialog and oldstrata then
586 dialog:SetFrameStrata(oldstrata)
587 end
588 AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
589 del(info)
590 end
591 t.OnCancel = function()
592 if dialog and oldstrata then
593 dialog:SetFrameStrata(oldstrata)
594 end
595 AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
596 del(info)
597 end
598 for i = 1, select("#", ...) do
599 t[i] = select(i, ...) or false
600 end
601 t.timeout = 0
602 t.whileDead = 1
603 t.hideOnEscape = 1
604
605 dialog = StaticPopup_Show("ACECONFIGDIALOG30_CONFIRM_DIALOG")
606 if dialog then
607 oldstrata = dialog:GetFrameStrata()
608 dialog:SetFrameStrata("TOOLTIP")
609 end
610 end
611
612 local function ActivateControl(widget, event, ...)
613 --This function will call the set / execute handler for the widget
614 --widget:GetUserDataTable() contains the needed info
615 local user = widget:GetUserDataTable()
616 local option = user.option
617 local options = user.options
618 local path = user.path
619 local info = new()
620
621 local func
622 local group = options
623 local funcname = GetFuncName(option)
624 local handler
625 local confirm
626 local validate
627 --build the info table containing the path
628 -- pick up functions while traversing the tree
629 if group[funcname] ~= nil then
630 func = group[funcname]
631 end
632 handler = group.handler or handler
633 confirm = group.confirm
634 validate = group.validate
635 for i = 1, #path do
636 local v = path[i]
637 group = GetSubOption(group, v)
638 info[i] = v
639 if group[funcname] ~= nil then
640 func = group[funcname]
641 end
642 handler = group.handler or handler
643 if group.confirm ~= nil then
644 confirm = group.confirm
645 end
646 if group.validate ~= nil then
647 validate = group.validate
648 end
649 end
650
651 info.options = options
652 info.appName = user.appName
653 info.arg = option.arg
654 info.handler = handler
655 info.option = option
656 info.type = option.type
657 info.uiType = "dialog"
658 info.uiName = MAJOR
659
660 local name
661 if type(option.name) == "function" then
662 name = option.name(info)
663 elseif type(option.name) == "string" then
664 name = option.name
665 else
666 name = ""
667 end
668 local usage = option.usage
669 local pattern = option.pattern
670
671 local validated = true
672
673 if option.type == "input" then
674 if type(pattern)=="string" then
675 if not strmatch(..., pattern) then
676 validated = false
677 end
678 end
679 end
680
681 local success
682 if validated and option.type ~= "execute" then
683 if type(validate) == "string" then
684 if handler and handler[validate] then
685 success, validated = safecall(handler[validate], handler, info, ...)
686 if not success then validated = false end
687 else
688 error(format("Method %s doesn't exist in handler for type execute", validate))
689 end
690 elseif type(validate) == "function" then
691 success, validated = safecall(validate, info, ...)
692 if not success then validated = false end
693 end
694 end
695
696 local rootframe = user.rootframe
697 if type(validated) == "string" then
698 --validate function returned a message to display
699 if rootframe.SetStatusText then
700 rootframe:SetStatusText(validated)
701 else
702 -- TODO: do something else.
703 end
704 PlaySound("igPlayerInviteDecline")
705 del(info)
706 return true
707 elseif not validated then
708 --validate returned false
709 if rootframe.SetStatusText then
710 if usage then
711 rootframe:SetStatusText(name..": "..usage)
712 else
713 if pattern then
714 rootframe:SetStatusText(name..": Expected "..pattern)
715 else
716 rootframe:SetStatusText(name..": Invalid Value")
717 end
718 end
719 else
720 -- TODO: do something else
721 end
722 PlaySound("igPlayerInviteDecline")
723 del(info)
724 return true
725 else
726
727 local confirmText = option.confirmText
728 --call confirm func/method
729 if type(confirm) == "string" then
730 if handler and handler[confirm] then
731 success, confirm = safecall(handler[confirm], handler, info, ...)
732 if success and type(confirm) == "string" then
733 confirmText = confirm
734 confirm = true
735 elseif not success then
736 confirm = false
737 end
738 else
739 error(format("Method %s doesn't exist in handler for type confirm", confirm))
740 end
741 elseif type(confirm) == "function" then
742 success, confirm = safecall(confirm, info, ...)
743 if success and type(confirm) == "string" then
744 confirmText = confirm
745 confirm = true
746 elseif not success then
747 confirm = false
748 end
749 end
750
751 --confirm if needed
752 if type(confirm) == "boolean" then
753 if confirm then
754 if not confirmText then
755 local name, desc = option.name, option.desc
756 if type(name) == "function" then
757 name = name(info)
758 end
759 if type(desc) == "function" then
760 desc = desc(info)
761 end
762 confirmText = name
763 if desc then
764 confirmText = confirmText.." - "..desc
765 end
766 end
767
768 local iscustom = user.rootframe:GetUserData("iscustom")
769 local rootframe
770
771 if iscustom then
772 rootframe = user.rootframe
773 end
774 local basepath = user.rootframe:GetUserData("basepath")
775 if type(func) == "string" then
776 if handler and handler[func] then
777 confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...)
778 else
779 error(format("Method %s doesn't exist in handler for type func", func))
780 end
781 elseif type(func) == "function" then
782 confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...)
783 end
784 --func will be called and info deleted when the confirm dialog is responded to
785 return
786 end
787 end
788
789 --call the function
790 if type(func) == "string" then
791 if handler and handler[func] then
792 safecall(handler[func],handler, info, ...)
793 else
794 error(format("Method %s doesn't exist in handler for type func", func))
795 end
796 elseif type(func) == "function" then
797 safecall(func,info, ...)
798 end
799
800
801
802 local iscustom = user.rootframe:GetUserData("iscustom")
803 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
804 --full refresh of the frame, some controls dont cause this on all events
805 if option.type == "color" then
806 if event == "OnValueConfirmed" then
807
808 if iscustom then
809 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
810 else
811 AceConfigDialog:Open(user.appName, unpack(basepath))
812 end
813 end
814 elseif option.type == "range" then
815 if event == "OnMouseUp" then
816 if iscustom then
817 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
818 else
819 AceConfigDialog:Open(user.appName, unpack(basepath))
820 end
821 end
822 --multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed'
823 elseif option.type == "multiselect" then
824 user.valuechanged = true
825 else
826 if iscustom then
827 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
828 else
829 AceConfigDialog:Open(user.appName, unpack(basepath))
830 end
831 end
832
833 end
834 del(info)
835 end
836
837 local function ActivateSlider(widget, event, value)
838 local option = widget:GetUserData("option")
839 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
840 if min then
841 if step then
842 value = math_floor((value - min) / step + 0.5) * step + min
843 end
844 value = math_max(value, min)
845 end
846 if max then
847 value = math_min(value, max)
848 end
849 ActivateControl(widget,event,value)
850 end
851
852 --called from a checkbox that is part of an internally created multiselect group
853 --this type is safe to refresh on activation of one control
854 local function ActivateMultiControl(widget, event, ...)
855 ActivateControl(widget, event, widget:GetUserData("value"), ...)
856 local user = widget:GetUserDataTable()
857 local iscustom = user.rootframe:GetUserData("iscustom")
858 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
859 if iscustom then
860 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
861 else
862 AceConfigDialog:Open(user.appName, unpack(basepath))
863 end
864 end
865
866 local function MultiControlOnClosed(widget, event, ...)
867 local user = widget:GetUserDataTable()
868 if user.valuechanged then
869 local iscustom = user.rootframe:GetUserData("iscustom")
870 local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
871 if iscustom then
872 AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
873 else
874 AceConfigDialog:Open(user.appName, unpack(basepath))
875 end
876 end
877 end
878
879 local function FrameOnClose(widget, event)
880 local appName = widget:GetUserData("appName")
881 AceConfigDialog.OpenFrames[appName] = nil
882 gui:Release(widget)
883 end
884
885 local function CheckOptionHidden(option, options, path, appName)
886 --check for a specific boolean option
887 local hidden = pickfirstset(option.dialogHidden,option.guiHidden)
888 if hidden ~= nil then
889 return hidden
890 end
891
892 return GetOptionsMemberValue("hidden", option, options, path, appName)
893 end
894
895 local function CheckOptionDisabled(option, options, path, appName)
896 --check for a specific boolean option
897 local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled)
898 if disabled ~= nil then
899 return disabled
900 end
901
902 return GetOptionsMemberValue("disabled", option, options, path, appName)
903 end
904 --[[
905 local function BuildTabs(group, options, path, appName)
906 local tabs = new()
907 local text = new()
908 local keySort = new()
909 local opts = new()
910
911 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
912
913 for i = 1, #keySort do
914 local k = keySort[i]
915 local v = opts[k]
916 if v.type == "group" then
917 path[#path+1] = k
918 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
919 local hidden = CheckOptionHidden(v, options, path, appName)
920 if not inline and not hidden then
921 tinsert(tabs, k)
922 text[k] = GetOptionsMemberValue("name", v, options, path, appName)
923 end
924 path[#path] = nil
925 end
926 end
927
928 del(keySort)
929 del(opts)
930
931 return tabs, text
932 end
933 ]]
934 local function BuildSelect(group, options, path, appName)
935 local groups = new()
936 local keySort = new()
937 local opts = new()
938
939 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
940
941 for i = 1, #keySort do
942 local k = keySort[i]
943 local v = opts[k]
944 if v.type == "group" then
945 path[#path+1] = k
946 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
947 local hidden = CheckOptionHidden(v, options, path, appName)
948 if not inline and not hidden then
949 groups[k] = GetOptionsMemberValue("name", v, options, path, appName)
950 end
951 path[#path] = nil
952 end
953 end
954
955 del(keySort)
956 del(opts)
957
958 return groups
959 end
960
961 local function BuildSubGroups(group, tree, options, path, appName)
962 local keySort = new()
963 local opts = new()
964
965 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
966
967 for i = 1, #keySort do
968 local k = keySort[i]
969 local v = opts[k]
970 if v.type == "group" then
971 path[#path+1] = k
972 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
973 local hidden = CheckOptionHidden(v, options, path, appName)
974 if not inline and not hidden then
975 local entry = new()
976 entry.value = k
977 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
978 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
979 entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName)
980 entry.disabled = CheckOptionDisabled(v, options, path, appName)
981 if not tree.children then tree.children = new() end
982 tinsert(tree.children,entry)
983 if (v.childGroups or "tree") == "tree" then
984 BuildSubGroups(v,entry, options, path, appName)
985 end
986 end
987 path[#path] = nil
988 end
989 end
990
991 del(keySort)
992 del(opts)
993 end
994
995 local function BuildGroups(group, options, path, appName, recurse)
996 local tree = new()
997 local keySort = new()
998 local opts = new()
999
1000 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1001
1002 for i = 1, #keySort do
1003 local k = keySort[i]
1004 local v = opts[k]
1005 if v.type == "group" then
1006 path[#path+1] = k
1007 local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1008 local hidden = CheckOptionHidden(v, options, path, appName)
1009 if not inline and not hidden then
1010 local entry = new()
1011 entry.value = k
1012 entry.text = GetOptionsMemberValue("name", v, options, path, appName)
1013 entry.icon = GetOptionsMemberValue("icon", v, options, path, appName)
1014 entry.disabled = CheckOptionDisabled(v, options, path, appName)
1015 tinsert(tree,entry)
1016 if recurse and (v.childGroups or "tree") == "tree" then
1017 BuildSubGroups(v,entry, options, path, appName)
1018 end
1019 end
1020 path[#path] = nil
1021 end
1022 end
1023 del(keySort)
1024 del(opts)
1025 return tree
1026 end
1027
1028 local function InjectInfo(control, options, option, path, rootframe, appName)
1029 local user = control:GetUserDataTable()
1030 for i = 1, #path do
1031 user[i] = path[i]
1032 end
1033 user.rootframe = rootframe
1034 user.option = option
1035 user.options = options
1036 user.path = copy(path)
1037 user.appName = appName
1038 control:SetCallback("OnRelease", CleanUserData)
1039 control:SetCallback("OnLeave", OptionOnMouseLeave)
1040 control:SetCallback("OnEnter", OptionOnMouseOver)
1041 end
1042
1043
1044 --[[
1045 options - root of the options table being fed
1046 container - widget that controls will be placed in
1047 rootframe - Frame object the options are in
1048 path - table with the keys to get to the group being fed
1049 --]]
1050
1051 local function FeedOptions(appName, options,container,rootframe,path,group,inline)
1052 local keySort = new()
1053 local opts = new()
1054
1055 BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
1056
1057 for i = 1, #keySort do
1058 local k = keySort[i]
1059 local v = opts[k]
1060 tinsert(path, k)
1061 local hidden = CheckOptionHidden(v, options, path, appName)
1062 local name = GetOptionsMemberValue("name", v, options, path, appName)
1063 if not hidden then
1064 if v.type == "group" then
1065 if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
1066 --Inline group
1067 local GroupContainer
1068 if name and name ~= "" then
1069 GroupContainer = gui:Create("InlineGroup")
1070 GroupContainer:SetTitle(name or "")
1071 else
1072 GroupContainer = gui:Create("SimpleGroup")
1073 end
1074
1075 GroupContainer.width = "fill"
1076 GroupContainer:SetLayout("flow")
1077 container:AddChild(GroupContainer)
1078 FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
1079 end
1080 else
1081 --Control to feed
1082 local control
1083
1084 local name = GetOptionsMemberValue("name", v, options, path, appName)
1085
1086 if v.type == "execute" then
1087
1088 local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1089 local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1090
1091 if type(image) == "string" then
1092 control = gui:Create("Icon")
1093 if not width then
1094 width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1095 end
1096 if not height then
1097 height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1098 end
1099 if type(imageCoords) == "table" then
1100 control:SetImage(image, unpack(imageCoords))
1101 else
1102 control:SetImage(image)
1103 end
1104 if type(width) ~= "number" then
1105 width = 32
1106 end
1107 if type(height) ~= "number" then
1108 height = 32
1109 end
1110 control:SetImageSize(width, height)
1111 control:SetLabel(name)
1112 else
1113 control = gui:Create("Button")
1114 control:SetText(name)
1115 end
1116 control:SetCallback("OnClick",ActivateControl)
1117
1118 elseif v.type == "input" then
1119 local controlType = v.dialogControl or v.control or (v.multiline and "MultiLineEditBox") or "EditBox"
1120 control = gui:Create(controlType)
1121 if not control then
1122 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1123 control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox")
1124 end
1125
1126 if v.multiline and control.SetNumLines then
1127 control:SetNumLines(tonumber(v.multiline) or 4)
1128 end
1129 control:SetLabel(name)
1130 control:SetCallback("OnEnterPressed",ActivateControl)
1131 local text = GetOptionsMemberValue("get",v, options, path, appName)
1132 if type(text) ~= "string" then
1133 text = ""
1134 end
1135 control:SetText(text)
1136
1137 elseif v.type == "toggle" then
1138 control = gui:Create("CheckBox")
1139 control:SetLabel(name)
1140 control:SetTriState(v.tristate)
1141 local value = GetOptionsMemberValue("get",v, options, path, appName)
1142 control:SetValue(value)
1143 control:SetCallback("OnValueChanged",ActivateControl)
1144
1145 if v.descStyle == "inline" then
1146 local desc = GetOptionsMemberValue("desc", v, options, path, appName)
1147 control:SetDescription(desc)
1148 end
1149
1150 local image = GetOptionsMemberValue("image", v, options, path, appName)
1151 local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName)
1152
1153 if type(image) == "string" then
1154 if type(imageCoords) == "table" then
1155 control:SetImage(image, unpack(imageCoords))
1156 else
1157 control:SetImage(image)
1158 end
1159 end
1160 elseif v.type == "range" then
1161 control = gui:Create("Slider")
1162 control:SetLabel(name)
1163 control:SetSliderValues(v.softMin or v.min or 0, v.softMax or v.max or 100, v.bigStep or v.step or 0)
1164 control:SetIsPercent(v.isPercent)
1165 local value = GetOptionsMemberValue("get",v, options, path, appName)
1166 if type(value) ~= "number" then
1167 value = 0
1168 end
1169 control:SetValue(value)
1170 control:SetCallback("OnValueChanged",ActivateSlider)
1171 control:SetCallback("OnMouseUp",ActivateSlider)
1172
1173 elseif v.type == "select" then
1174 local values = GetOptionsMemberValue("values", v, options, path, appName)
1175 local controlType = v.dialogControl or v.control or "Dropdown"
1176 control = gui:Create(controlType)
1177 if not control then
1178 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1179 control = gui:Create("Dropdown")
1180 end
1181 control:SetLabel(name)
1182 control:SetList(values)
1183 local value = GetOptionsMemberValue("get",v, options, path, appName)
1184 if not values[value] then
1185 value = nil
1186 end
1187 control:SetValue(value)
1188 control:SetCallback("OnValueChanged",ActivateControl)
1189
1190 elseif v.type == "multiselect" then
1191 local values = GetOptionsMemberValue("values", v, options, path, appName)
1192 local disabled = CheckOptionDisabled(v, options, path, appName)
1193
1194 local controlType = v.dialogControl or v.control
1195
1196 local valuesort = new()
1197 if values then
1198 for value, text in pairs(values) do
1199 tinsert(valuesort, value)
1200 end
1201 end
1202 tsort(valuesort)
1203
1204 if controlType then
1205 control = gui:Create(controlType)
1206 if not control then
1207 geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType)))
1208 end
1209 end
1210 if control then
1211 control:SetMultiselect(true)
1212 control:SetLabel(name)
1213 control:SetList(values)
1214 control:SetDisabled(disabled)
1215 control:SetCallback("OnValueChanged",ActivateControl)
1216 control:SetCallback("OnClosed", MultiControlOnClosed)
1217 local width = GetOptionsMemberValue("width",v,options,path,appName)
1218 if width == "double" then
1219 control:SetWidth(width_multiplier * 2)
1220 elseif width == "half" then
1221 control:SetWidth(width_multiplier / 2)
1222 elseif width == "full" then
1223 control.width = "fill"
1224 else
1225 control:SetWidth(width_multiplier)
1226 end
1227 --check:SetTriState(v.tristate)
1228 for i = 1, #valuesort do
1229 local key = valuesort[i]
1230 local value = GetOptionsMemberValue("get",v, options, path, appName, key)
1231 control:SetItemValue(key,value)
1232 end
1233 else
1234 control = gui:Create("InlineGroup")
1235 control:SetLayout("Flow")
1236 control:SetTitle(name)
1237 control.width = "fill"
1238
1239 control:PauseLayout()
1240 local width = GetOptionsMemberValue("width",v,options,path,appName)
1241 for i = 1, #valuesort do
1242 local value = valuesort[i]
1243 local text = values[value]
1244 local check = gui:Create("CheckBox")
1245 check:SetLabel(text)
1246 check:SetUserData("value", value)
1247 check:SetUserData("text", text)
1248 check:SetDisabled(disabled)
1249 check:SetTriState(v.tristate)
1250 check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
1251 check:SetCallback("OnValueChanged",ActivateMultiControl)
1252 InjectInfo(check, options, v, path, rootframe, appName)
1253 control:AddChild(check)
1254 if width == "double" then
1255 check:SetWidth(width_multiplier * 2)
1256 elseif width == "half" then
1257 check:SetWidth(width_multiplier / 2)
1258 elseif width == "full" then
1259 check.width = "fill"
1260 else
1261 check:SetWidth(width_multiplier)
1262 end
1263 end
1264 control:ResumeLayout()
1265 control:DoLayout()
1266
1267
1268 end
1269
1270 del(valuesort)
1271
1272 elseif v.type == "color" then
1273 control = gui:Create("ColorPicker")
1274 control:SetLabel(name)
1275 control:SetHasAlpha(v.hasAlpha)
1276 control:SetColor(GetOptionsMemberValue("get",v, options, path, appName))
1277 control:SetCallback("OnValueChanged",ActivateControl)
1278 control:SetCallback("OnValueConfirmed",ActivateControl)
1279
1280 elseif v.type == "keybinding" then
1281 control = gui:Create("Keybinding")
1282 control:SetLabel(name)
1283 control:SetKey(GetOptionsMemberValue("get",v, options, path, appName))
1284 control:SetCallback("OnKeyChanged",ActivateControl)
1285
1286 elseif v.type == "header" then
1287 control = gui:Create("Heading")
1288 control:SetText(name)
1289 control.width = "fill"
1290
1291 elseif v.type == "description" then
1292 control = gui:Create("Label")
1293 control:SetText(name)
1294
1295 local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName)
1296 if fontSize == "medium" then
1297 control:SetFontObject(GameFontHighlight)
1298 elseif fontSize == "large" then
1299 control:SetFontObject(GameFontHighlightLarge)
1300 else -- small or invalid
1301 control:SetFontObject(GameFontHighlightSmall)
1302 end
1303
1304 local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
1305 local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
1306
1307 if type(image) == "string" then
1308 if not width then
1309 width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
1310 end
1311 if not height then
1312 height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
1313 end
1314 if type(imageCoords) == "table" then
1315 control:SetImage(image, unpack(imageCoords))
1316 else
1317 control:SetImage(image)
1318 end
1319 if type(width) ~= "number" then
1320 width = 32
1321 end
1322 if type(height) ~= "number" then
1323 height = 32
1324 end
1325 control:SetImageSize(width, height)
1326 end
1327 local width = GetOptionsMemberValue("width",v,options,path,appName)
1328 control.width = not width and "fill"
1329 end
1330
1331 --Common Init
1332 if control then
1333 if control.width ~= "fill" then
1334 local width = GetOptionsMemberValue("width",v,options,path,appName)
1335 if width == "double" then
1336 control:SetWidth(width_multiplier * 2)
1337 elseif width == "half" then
1338 control:SetWidth(width_multiplier / 2)
1339 elseif width == "full" then
1340 control.width = "fill"
1341 else
1342 control:SetWidth(width_multiplier)
1343 end
1344 end
1345 if control.SetDisabled then
1346 local disabled = CheckOptionDisabled(v, options, path, appName)
1347 control:SetDisabled(disabled)
1348 end
1349
1350 InjectInfo(control, options, v, path, rootframe, appName)
1351 container:AddChild(control)
1352 end
1353
1354 end
1355 end
1356 tremove(path)
1357 end
1358 container:ResumeLayout()
1359 container:DoLayout()
1360 del(keySort)
1361 del(opts)
1362 end
1363
1364 local function BuildPath(path, ...)
1365 for i = 1, select("#",...) do
1366 tinsert(path, (select(i,...)))
1367 end
1368 end
1369
1370
1371 local function TreeOnButtonEnter(widget, event, uniquevalue, button)
1372 local user = widget:GetUserDataTable()
1373 if not user then return end
1374 local options = user.options
1375 local option = user.option
1376 local path = user.path
1377 local appName = user.appName
1378
1379 local feedpath = new()
1380 for i = 1, #path do
1381 feedpath[i] = path[i]
1382 end
1383
1384 BuildPath(feedpath, ("\001"):split(uniquevalue))
1385 local group = options
1386 for i = 1, #feedpath do
1387 if not group then return end
1388 group = GetSubOption(group, feedpath[i])
1389 end
1390
1391 local name = GetOptionsMemberValue("name", group, options, feedpath, appName)
1392 local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName)
1393
1394 GameTooltip:SetOwner(button, "ANCHOR_NONE")
1395 if widget.type == "TabGroup" then
1396 GameTooltip:SetPoint("BOTTOM",button,"TOP")
1397 else
1398 GameTooltip:SetPoint("LEFT",button,"RIGHT")
1399 end
1400
1401 GameTooltip:SetText(name, 1, .82, 0, 1)
1402
1403 if type(desc) == "string" then
1404 GameTooltip:AddLine(desc, 1, 1, 1, 1)
1405 end
1406
1407 GameTooltip:Show()
1408 end
1409
1410 local function TreeOnButtonLeave(widget, event, value, button)
1411 GameTooltip:Hide()
1412 end
1413
1414
1415 local function GroupExists(appName, options, path, uniquevalue)
1416 if not uniquevalue then return false end
1417
1418 local feedpath = new()
1419 local temppath = new()
1420 for i = 1, #path do
1421 feedpath[i] = path[i]
1422 end
1423
1424 BuildPath(feedpath, ("\001"):split(uniquevalue))
1425
1426 local group = options
1427 for i = 1, #feedpath do
1428 local v = feedpath[i]
1429 temppath[i] = v
1430 group = GetSubOption(group, v)
1431
1432 if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then
1433 del(feedpath)
1434 del(temppath)
1435 return false
1436 end
1437 end
1438 del(feedpath)
1439 del(temppath)
1440 return true
1441 end
1442
1443 local function GroupSelected(widget, event, uniquevalue)
1444
1445 local user = widget:GetUserDataTable()
1446
1447 local options = user.options
1448 local option = user.option
1449 local path = user.path
1450 local rootframe = user.rootframe
1451
1452 local feedpath = new()
1453 for i = 1, #path do
1454 feedpath[i] = path[i]
1455 end
1456
1457 BuildPath(feedpath, ("\001"):split(uniquevalue))
1458 local group = options
1459 for i = 1, #feedpath do
1460 group = GetSubOption(group, feedpath[i])
1461 end
1462 widget:ReleaseChildren()
1463 AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath)
1464
1465 del(feedpath)
1466 end
1467
1468
1469
1470 --[[
1471 -- INTERNAL --
1472 This function will feed one group, and any inline child groups into the given container
1473 Select Groups will only have the selection control (tree, tabs, dropdown) fed in
1474 and have a group selected, this event will trigger the feeding of child groups
1475
1476 Rules:
1477 If the group is Inline, FeedOptions
1478 If the group has no child groups, FeedOptions
1479
1480 If the group is a tab or select group, FeedOptions then add the Group Control
1481 If the group is a tree group FeedOptions then
1482 its parent isnt a tree group: then add the tree control containing this and all child tree groups
1483 if its parent is a tree group, its already a node on a tree
1484 --]]
1485
1486 function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot)
1487 local group = options
1488 --follow the path to get to the curent group
1489 local inline
1490 local grouptype, parenttype = options.childGroups, "none"
1491
1492
1493 for i = 1, #path do
1494 local v = path[i]
1495 group = GetSubOption(group, v)
1496 inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
1497 parenttype = grouptype
1498 grouptype = group.childGroups
1499 end
1500
1501 if not parenttype then
1502 parenttype = "tree"
1503 end
1504
1505 --check if the group has child groups
1506 local hasChildGroups
1507 for k, v in pairs(group.args) do
1508 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1509 hasChildGroups = true
1510 end
1511 end
1512 if group.plugins then
1513 for plugin, t in pairs(group.plugins) do
1514 for k, v in pairs(t) do
1515 if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then
1516 hasChildGroups = true
1517 end
1518 end
1519 end
1520 end
1521
1522 container:SetLayout("flow")
1523 local scroll
1524
1525 --Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on
1526 if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and (parenttype == "tree" and not isRoot)) then
1527 if container.type ~= "InlineGroup" and container.type ~= "SimpleGroup" then
1528 scroll = gui:Create("ScrollFrame")
1529 scroll:SetLayout("flow")
1530 scroll.width = "fill"
1531 scroll.height = "fill"
1532 container:SetLayout("fill")
1533 container:AddChild(scroll)
1534 container = scroll
1535 end
1536 end
1537
1538 FeedOptions(appName,options,container,rootframe,path,group,nil)
1539
1540 if scroll then
1541 container:PerformLayout()
1542 local status = self:GetStatusTable(appName, path)
1543 if not status.scroll then
1544 status.scroll = {}
1545 end
1546 scroll:SetStatusTable(status.scroll)
1547 end
1548
1549 if hasChildGroups and not inline then
1550 local name = GetOptionsMemberValue("name", group, options, path, appName)
1551 if grouptype == "tab" then
1552
1553 local tab = gui:Create("TabGroup")
1554 InjectInfo(tab, options, group, path, rootframe, appName)
1555 tab:SetCallback("OnGroupSelected", GroupSelected)
1556 tab:SetCallback("OnTabEnter", TreeOnButtonEnter)
1557 tab:SetCallback("OnTabLeave", TreeOnButtonLeave)
1558
1559 local status = AceConfigDialog:GetStatusTable(appName, path)
1560 if not status.groups then
1561 status.groups = {}
1562 end
1563 tab:SetStatusTable(status.groups)
1564 tab.width = "fill"
1565 tab.height = "fill"
1566
1567 local tabs = BuildGroups(group, options, path, appName)
1568 tab:SetTabs(tabs)
1569 tab:SetUserData("tablist", tabs)
1570
1571 for i = 1, #tabs do
1572 local entry = tabs[i]
1573 if not entry.disabled then
1574 tab:SelectTab((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1575 break
1576 end
1577 end
1578
1579 container:AddChild(tab)
1580
1581 elseif grouptype == "select" then
1582
1583 local select = gui:Create("DropdownGroup")
1584 select:SetTitle(name)
1585 InjectInfo(select, options, group, path, rootframe, appName)
1586 select:SetCallback("OnGroupSelected", GroupSelected)
1587 local status = AceConfigDialog:GetStatusTable(appName, path)
1588 if not status.groups then
1589 status.groups = {}
1590 end
1591 select:SetStatusTable(status.groups)
1592 local grouplist = BuildSelect(group, options, path, appName)
1593 select:SetGroupList(grouplist)
1594 select:SetUserData("grouplist", grouplist)
1595 local firstgroup
1596 for k, v in pairs(grouplist) do
1597 if not firstgroup or k < firstgroup then
1598 firstgroup = k
1599 end
1600 end
1601
1602 if firstgroup then
1603 select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup)
1604 end
1605
1606 select.width = "fill"
1607 select.height = "fill"
1608
1609 container:AddChild(select)
1610
1611 --assume tree group by default
1612 --if parenttype is tree then this group is already a node on that tree
1613 elseif (parenttype ~= "tree") or isRoot then
1614 local tree = gui:Create("TreeGroup")
1615 InjectInfo(tree, options, group, path, rootframe, appName)
1616 tree:EnableButtonTooltips(false)
1617
1618 tree.width = "fill"
1619 tree.height = "fill"
1620
1621 tree:SetCallback("OnGroupSelected", GroupSelected)
1622 tree:SetCallback("OnButtonEnter", TreeOnButtonEnter)
1623 tree:SetCallback("OnButtonLeave", TreeOnButtonLeave)
1624
1625 local status = AceConfigDialog:GetStatusTable(appName, path)
1626 if not status.groups then
1627 status.groups = {}
1628 end
1629 local treedefinition = BuildGroups(group, options, path, appName, true)
1630 tree:SetStatusTable(status.groups)
1631
1632 tree:SetTree(treedefinition)
1633 tree:SetUserData("tree",treedefinition)
1634
1635 for i = 1, #treedefinition do
1636 local entry = treedefinition[i]
1637 if not entry.disabled then
1638 tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
1639 break
1640 end
1641 end
1642
1643 container:AddChild(tree)
1644 end
1645 end
1646 end
1647
1648 local old_CloseSpecialWindows
1649
1650
1651 local function RefreshOnUpdate(this)
1652 for appName in pairs(this.closing) do
1653 if AceConfigDialog.OpenFrames[appName] then
1654 AceConfigDialog.OpenFrames[appName]:Hide()
1655 end
1656 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1657 for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1658 if not widget:IsVisible() then
1659 widget:ReleaseChildren()
1660 end
1661 end
1662 end
1663 this.closing[appName] = nil
1664 end
1665
1666 if this.closeAll then
1667 for k, v in pairs(AceConfigDialog.OpenFrames) do
1668 if not this.closeAllOverride[k] then
1669 v:Hide()
1670 end
1671 end
1672 this.closeAll = nil
1673 wipe(this.closeAllOverride)
1674 end
1675
1676 for appName in pairs(this.apps) do
1677 if AceConfigDialog.OpenFrames[appName] then
1678 local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable()
1679 AceConfigDialog:Open(appName, unpack(user.basepath or emptyTbl))
1680 end
1681 if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then
1682 for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
1683 local user = widget:GetUserDataTable()
1684 if widget:IsVisible() then
1685 AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(user.basepath or emptyTbl))
1686 end
1687 end
1688 end
1689 this.apps[appName] = nil
1690 end
1691 this:SetScript("OnUpdate", nil)
1692 end
1693
1694 -- Upgrade the OnUpdate script as well, if needed.
1695 if AceConfigDialog.frame:GetScript("OnUpdate") then
1696 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1697 end
1698
1699 --- Close all open options windows
1700 function AceConfigDialog:CloseAll()
1701 AceConfigDialog.frame.closeAll = true
1702 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1703 if next(self.OpenFrames) then
1704 return true
1705 end
1706 end
1707
1708 --- Close a specific options window.
1709 -- @param appName The application name as given to `:RegisterOptionsTable()`
1710 function AceConfigDialog:Close(appName)
1711 if self.OpenFrames[appName] then
1712 AceConfigDialog.frame.closing[appName] = true
1713 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1714 return true
1715 end
1716 end
1717
1718 -- Internal -- Called by AceConfigRegistry
1719 function AceConfigDialog:ConfigTableChanged(event, appName)
1720 AceConfigDialog.frame.apps[appName] = true
1721 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1722 end
1723
1724 reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged")
1725
1726 --- Sets the default size of the options window for a specific application.
1727 -- @param appName The application name as given to `:RegisterOptionsTable()`
1728 -- @param width The default width
1729 -- @param height The default height
1730 function AceConfigDialog:SetDefaultSize(appName, width, height)
1731 local status = AceConfigDialog:GetStatusTable(appName)
1732 if type(width) == "number" and type(height) == "number" then
1733 status.width = width
1734 status.height = height
1735 end
1736 end
1737
1738 --- Open an option window at the specified path (if any).
1739 -- This function can optionally feed the group into a pre-created container
1740 -- instead of creating a new container frame.
1741 -- @paramsig appName [, container][, ...]
1742 -- @param appName The application name as given to `:RegisterOptionsTable()`
1743 -- @param container An optional container frame to feed the options into
1744 -- @param ... The path to open after creating the options window (see `:SelectGroup` for details)
1745 function AceConfigDialog:Open(appName, container, ...)
1746 if not old_CloseSpecialWindows then
1747 old_CloseSpecialWindows = CloseSpecialWindows
1748 CloseSpecialWindows = function()
1749 local found = old_CloseSpecialWindows()
1750 return self:CloseAll() or found
1751 end
1752 end
1753 local app = reg:GetOptionsTable(appName)
1754 if not app then
1755 error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
1756 end
1757 local options = app("dialog", MAJOR)
1758
1759 local f
1760
1761 local path = new()
1762 local name = GetOptionsMemberValue("name", options, options, path, appName)
1763
1764 --If an optional path is specified add it to the path table before feeding the options
1765 --as container is optional as well it may contain the first element of the path
1766 if type(container) == "string" then
1767 tinsert(path, container)
1768 container = nil
1769 end
1770 for n = 1, select("#",...) do
1771 tinsert(path, (select(n, ...)))
1772 end
1773
1774 --if a container is given feed into that
1775 if container then
1776 f = container
1777 f:ReleaseChildren()
1778 f:SetUserData("appName", appName)
1779 f:SetUserData("iscustom", true)
1780 if #path > 0 then
1781 f:SetUserData("basepath", copy(path))
1782 end
1783 local status = AceConfigDialog:GetStatusTable(appName)
1784 if not status.width then
1785 status.width = 700
1786 end
1787 if not status.height then
1788 status.height = 500
1789 end
1790 if f.SetStatusTable then
1791 f:SetStatusTable(status)
1792 end
1793 if f.SetTitle then
1794 f:SetTitle(name or "")
1795 end
1796 else
1797 if not self.OpenFrames[appName] then
1798 f = gui:Create("Frame")
1799 self.OpenFrames[appName] = f
1800 else
1801 f = self.OpenFrames[appName]
1802 end
1803 f:ReleaseChildren()
1804 f:SetCallback("OnClose", FrameOnClose)
1805 f:SetUserData("appName", appName)
1806 if #path > 0 then
1807 f:SetUserData("basepath", copy(path))
1808 end
1809 f:SetTitle(name or "")
1810 local status = AceConfigDialog:GetStatusTable(appName)
1811 f:SetStatusTable(status)
1812 end
1813
1814 self:FeedGroup(appName,options,f,f,path,true)
1815 if f.Show then
1816 f:Show()
1817 end
1818 del(path)
1819
1820 if AceConfigDialog.frame.closeAll then
1821 -- close all is set, but thats not good, since we're just opening here, so force it
1822 AceConfigDialog.frame.closeAllOverride[appName] = true
1823 end
1824 end
1825
1826 -- convert pre-39 BlizOptions structure to the new format
1827 if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then
1828 local old = AceConfigDialog.BlizOptions
1829 local new = {}
1830 for key, widget in pairs(old) do
1831 local appName = widget:GetUserData("appName")
1832 if not new[appName] then new[appName] = {} end
1833 new[appName][key] = widget
1834 end
1835 AceConfigDialog.BlizOptions = new
1836 else
1837 AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {}
1838 end
1839
1840 local function FeedToBlizPanel(widget, event)
1841 local path = widget:GetUserData("path")
1842 AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl))
1843 end
1844
1845 local function ClearBlizPanel(widget, event)
1846 local appName = widget:GetUserData("appName")
1847 AceConfigDialog.frame.closing[appName] = true
1848 AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
1849 end
1850
1851 --- Add an option table into the Blizzard Interface Options panel.
1852 -- You can optionally supply a descriptive name to use and a parent frame to use,
1853 -- as well as a path in the options table.\\
1854 -- If no name is specified, the appName will be used instead.
1855 --
1856 -- If you specify a proper `parent` (by name), the interface options will generate a
1857 -- tree layout. Note that only one level of children is supported, so the parent always
1858 -- has to be a head-level note.
1859 --
1860 -- This function returns a reference to the container frame registered with the Interface
1861 -- Options. You can use this reference to open the options with the API function
1862 -- `InterfaceOptionsFrame_OpenToCategory`.
1863 -- @param appName The application name as given to `:RegisterOptionsTable()`
1864 -- @param name A descriptive name to display in the options tree (defaults to appName)
1865 -- @param parent The parent to use in the interface options tree.
1866 -- @param ... The path in the options table to feed into the interface options panel.
1867 -- @return The reference to the frame registered into the Interface Options.
1868 function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
1869 local BlizOptions = AceConfigDialog.BlizOptions
1870
1871 local key = appName
1872 for n = 1, select("#", ...) do
1873 key = key.."\001"..select(n, ...)
1874 end
1875
1876 if not BlizOptions[appName] then
1877 BlizOptions[appName] = {}
1878 end
1879
1880 if not BlizOptions[appName][key] then
1881 local group = gui:Create("BlizOptionsGroup")
1882 BlizOptions[appName][key] = group
1883 group:SetName(name or appName, parent)
1884
1885 group:SetTitle(name or appName)
1886 group:SetUserData("appName", appName)
1887 if select("#", ...) > 0 then
1888 local path = {}
1889 for n = 1, select("#",...) do
1890 tinsert(path, (select(n, ...)))
1891 end
1892 group:SetUserData("path", path)
1893 end
1894 group:SetCallback("OnShow", FeedToBlizPanel)
1895 group:SetCallback("OnHide", ClearBlizPanel)
1896 InterfaceOptions_AddCategory(group.frame)
1897 return group.frame
1898 else
1899 error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2)
1900 end
1901 end