Mercurial > wow > inventory
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 |