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

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