Mercurial > wow > mailopener
view Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua @ 1:6f17035de058
MO will now request a server refresh when mail opening has finished, ignoring the timer.
(Collected Module) Fixed the time spent to only be displayed if it is being tracked.
The inventory full sound will properly be played again when your bags change.
| author | Zerotorescue |
|---|---|
| date | Sun, 05 Sep 2010 17:02:19 +0200 |
| parents | 823e33465b6e |
| children |
line wrap: on
line source
--- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables. -- @class file -- @name AceConfigDialog-3.0 -- @release $Id: AceConfigDialog-3.0.lua 958 2010-07-03 10:22:29Z nevcairiel $ local LibStub = LibStub local MAJOR, MINOR = "AceConfigDialog-3.0", 49 local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceConfigDialog then return end AceConfigDialog.OpenFrames = AceConfigDialog.OpenFrames or {} AceConfigDialog.Status = AceConfigDialog.Status or {} AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame") AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {} AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {} AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {} local gui = LibStub("AceGUI-3.0") local reg = LibStub("AceConfigRegistry-3.0") -- Lua APIs local tconcat, tinsert, tsort, tremove = table.concat, table.insert, table.sort, table.remove local strmatch, format = string.match, string.format local assert, loadstring, error = assert, loadstring, error local pairs, next, select, type, unpack, wipe = pairs, next, select, type, unpack, wipe local rawset, tostring, tonumber = rawset, tostring, tonumber local math_min, math_max, math_floor = math.min, math.max, math.floor -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded -- List them here for Mikk's FindGlobals script -- GLOBALS: NORMAL_FONT_COLOR, GameTooltip, StaticPopupDialogs, ACCEPT, CANCEL, StaticPopup_Show -- GLOBALS: PlaySound, GameFontHighlight, GameFontHighlightSmall, GameFontHighlightLarge -- GLOBALS: CloseSpecialWindows, InterfaceOptions_AddCategory, geterrorhandler local emptyTbl = {} --[[ xpcall safecall implementation ]] local xpcall = xpcall local function errorhandler(err) return geterrorhandler()(err) end local function CreateDispatcher(argCount) local code = [[ local xpcall, eh = ... local method, ARGS local function call() return method(ARGS) end local function dispatch(func, ...) method = func if not method then return end ARGS = ... return xpcall(call, eh) end return dispatch ]] local ARGS = {} for i = 1, argCount do ARGS[i] = "arg"..i end code = code:gsub("ARGS", tconcat(ARGS, ", ")) return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) end local Dispatchers = setmetatable({}, {__index=function(self, argCount) local dispatcher = CreateDispatcher(argCount) rawset(self, argCount, dispatcher) return dispatcher end}) Dispatchers[0] = function(func) return xpcall(func, errorhandler) end local function safecall(func, ...) return Dispatchers[select("#", ...)](func, ...) end local width_multiplier = 170 --[[ Group Types Tree - All Descendant Groups will all become nodes on the tree, direct child options will appear above the tree - Descendant Groups with inline=true and thier children will not become nodes Tab - Direct Child Groups will become tabs, direct child options will appear above the tab control - Grandchild groups will default to inline unless specified otherwise Select- Same as Tab but with entries in a dropdown rather than tabs Inline Groups - Will not become nodes of a select group, they will be effectivly part of thier parent group seperated by a border - If declared on a direct child of a root node of a select group, they will appear above the group container control - When a group is displayed inline, all descendants will also be inline members of the group ]] -- Recycling functions local new, del, copy --newcount, delcount,createdcount,cached = 0,0,0 do local pool = setmetatable({},{__mode="k"}) function new() --newcount = newcount + 1 local t = next(pool) if t then pool[t] = nil return t else --createdcount = createdcount + 1 return {} end end function copy(t) local c = new() for k, v in pairs(t) do c[k] = v end return c end function del(t) --delcount = delcount + 1 for k in pairs(t) do t[k] = nil end pool[t] = true end -- function cached() -- local n = 0 -- for k in pairs(pool) do -- n = n + 1 -- end -- return n -- end end -- picks the first non-nil value and returns it local function pickfirstset(...) for i=1,select("#",...) do if select(i,...)~=nil then return select(i,...) end end end --gets an option from a given group, checking plugins local function GetSubOption(group, key) if group.plugins then for plugin, t in pairs(group.plugins) do if t[key] then return t[key] end end end return group.args[key] end --Option member type definitions, used to decide how to access it --Is the member Inherited from parent options local isInherited = { set = true, get = true, func = true, confirm = true, validate = true, disabled = true, hidden = true } --Does a string type mean a literal value, instead of the default of a method of the handler local stringIsLiteral = { name = true, desc = true, icon = true, usage = true, width = true, image = true, fontSize = true, } --Is Never a function or method local allIsLiteral = { type = true, descStyle = true, imageWidth = true, imageHeight = true, } --gets the value for a member that could be a function --function refs are called with an info arg --every other type is returned local function GetOptionsMemberValue(membername, option, options, path, appName, ...) --get definition for the member local inherits = isInherited[membername] --get the member of the option, traversing the tree if it can be inherited local member if inherits then local group = options if group[membername] ~= nil then member = group[membername] end for i = 1, #path do group = GetSubOption(group, path[i]) if group[membername] ~= nil then member = group[membername] end end else member = option[membername] end --check if we need to call a functon, or if we have a literal value if ( not allIsLiteral[membername] ) and ( type(member) == "function" or ((not stringIsLiteral[membername]) and type(member) == "string") ) then --We have a function to call local info = new() --traverse the options table, picking up the handler and filling the info with the path local handler local group = options handler = group.handler or handler for i = 1, #path do group = GetSubOption(group, path[i]) info[i] = path[i] handler = group.handler or handler end info.options = options info.appName = appName info[0] = appName info.arg = option.arg info.handler = handler info.option = option info.type = option.type info.uiType = "dialog" info.uiName = MAJOR local a, b, c ,d --using 4 returns for the get of a color type, increase if a type needs more if type(member) == "function" then --Call the function a,b,c,d = member(info, ...) else --Call the method if handler and handler[member] then a,b,c,d = handler[member](handler, info, ...) else error(format("Method %s doesn't exist in handler for type %s", member, membername)) end end del(info) return a,b,c,d else --The value isnt a function to call, return it return member end end --[[calls an options function that could be inherited, method name or function ref local function CallOptionsFunction(funcname ,option, options, path, appName, ...) local info = new() local func local group = options local handler --build the info table containing the path -- pick up functions while traversing the tree if group[funcname] ~= nil then func = group[funcname] end handler = group.handler or handler for i, v in ipairs(path) do group = GetSubOption(group, v) info[i] = v if group[funcname] ~= nil then func = group[funcname] end handler = group.handler or handler end info.options = options info[0] = appName info.arg = option.arg local a, b, c ,d if type(func) == "string" then if handler and handler[func] then a,b,c,d = handler[func](handler, info, ...) else error(string.format("Method %s doesn't exist in handler for type func", func)) end elseif type(func) == "function" then a,b,c,d = func(info, ...) end del(info) return a,b,c,d end --]] --tables to hold orders and names for options being sorted, will be created with new() --prevents needing to call functions repeatedly while sorting local tempOrders local tempNames local function compareOptions(a,b) if not a then return true end if not b then return false end local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100 if OrderA == OrderB then local NameA = (type(tempNames[a] == "string") and tempNames[a]) or "" local NameB = (type(tempNames[b] == "string") and tempNames[b]) or "" return NameA:upper() < NameB:upper() end if OrderA < 0 then if OrderB > 0 then return false end else if OrderB < 0 then return true end end return OrderA < OrderB end --builds 2 tables out of an options group -- keySort, sorted keys -- opts, combined options from .plugins and args local function BuildSortedOptionsTable(group, keySort, opts, options, path, appName) tempOrders = new() tempNames = new() if group.plugins then for plugin, t in pairs(group.plugins) do for k, v in pairs(t) do if not opts[k] then tinsert(keySort, k) opts[k] = v path[#path+1] = k tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName) tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName) path[#path] = nil end end end end for k, v in pairs(group.args) do if not opts[k] then tinsert(keySort, k) opts[k] = v path[#path+1] = k tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName) tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName) path[#path] = nil end end tsort(keySort, compareOptions) del(tempOrders) del(tempNames) end local function DelTree(tree) if tree.children then local childs = tree.children for i = 1, #childs do DelTree(childs[i]) del(childs[i]) end del(childs) end end local function CleanUserData(widget, event) local user = widget:GetUserDataTable() if user.path then del(user.path) end if widget.type == "TreeGroup" then local tree = user.tree widget:SetTree(nil) if tree then for i = 1, #tree do DelTree(tree[i]) del(tree[i]) end del(tree) end end if widget.type == "TabGroup" then widget:SetTabs(nil) if user.tablist then del(user.tablist) end end if widget.type == "DropdownGroup" then widget:SetGroupList(nil) if user.grouplist then del(user.grouplist) end end end -- - Gets a status table for the given appname and options path. -- @param appName The application name as given to `:RegisterOptionsTable()` -- @param path The path to the options (a table with all group keys) -- @return function AceConfigDialog:GetStatusTable(appName, path) local status = self.Status if not status[appName] then status[appName] = {} status[appName].status = {} status[appName].children = {} end status = status[appName] if path then for i = 1, #path do local v = path[i] if not status.children[v] then status.children[v] = {} status.children[v].status = {} status.children[v].children = {} end status = status.children[v] end end return status.status end --- Selects the specified path in the options window. -- The path specified has to match the keys of the groups in the table. -- @param appName The application name as given to `:RegisterOptionsTable()` -- @param ... The path to the key that should be selected function AceConfigDialog:SelectGroup(appName, ...) local path = new() local app = reg:GetOptionsTable(appName) if not app then error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2) end local options = app("dialog", MAJOR) local group = options local status = self:GetStatusTable(appName, path) if not status.groups then status.groups = {} end status = status.groups local treevalue local treestatus for n = 1, select("#",...) do local key = select(n, ...) if group.childGroups == "tab" or group.childGroups == "select" then --if this is a tab or select group, select the group status.selected = key --children of this group are no longer extra levels of a tree treevalue = nil else --tree group by default if treevalue then --this is an extra level of a tree group, build a uniquevalue for it treevalue = treevalue.."\001"..key else --this is the top level of a tree group, the uniquevalue is the same as the key treevalue = key if not status.groups then status.groups = {} end --save this trees status table for any extra levels or groups treestatus = status end --make sure that the tree entry is open, and select it. --the selected group will be overwritten if a child is the final target but still needs to be open treestatus.selected = treevalue treestatus.groups[treevalue] = true end --move to the next group in the path group = GetSubOption(group, key) if not group then break end tinsert(path, key) status = self:GetStatusTable(appName, path) if not status.groups then status.groups = {} end status = status.groups end del(path) reg:NotifyChange(appName) end local function OptionOnMouseOver(widget, event) --show a tooltip/set the status bar to the desc text local user = widget:GetUserDataTable() local opt = user.option local options = user.options local path = user.path local appName = user.appName GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT") local name = GetOptionsMemberValue("name", opt, options, path, appName) local desc = GetOptionsMemberValue("desc", opt, options, path, appName) local usage = GetOptionsMemberValue("usage", opt, options, path, appName) local descStyle = opt.descStyle if descStyle and descStyle ~= "tooltip" then return end GameTooltip:SetText(name, 1, .82, 0, 1) if opt.type == "multiselect" then GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1) end if type(desc) == "string" then GameTooltip:AddLine(desc, 1, 1, 1, 1) end if type(usage) == "string" then GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) end GameTooltip:Show() end local function OptionOnMouseLeave(widget, event) GameTooltip:Hide() end local function GetFuncName(option) local type = option.type if type == "execute" then return "func" else return "set" end end local function confirmPopup(appName, rootframe, basepath, info, message, func, ...) if not StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] then StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] = {} end local t = StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] for k in pairs(t) do t[k] = nil end t.text = message t.button1 = ACCEPT t.button2 = CANCEL local dialog, oldstrata t.OnAccept = function() safecall(func, unpack(t)) if dialog and oldstrata then dialog:SetFrameStrata(oldstrata) end AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl)) del(info) end t.OnCancel = function() if dialog and oldstrata then dialog:SetFrameStrata(oldstrata) end AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl)) del(info) end for i = 1, select("#", ...) do t[i] = select(i, ...) or false end t.timeout = 0 t.whileDead = 1 t.hideOnEscape = 1 dialog = StaticPopup_Show("ACECONFIGDIALOG30_CONFIRM_DIALOG") if dialog then oldstrata = dialog:GetFrameStrata() dialog:SetFrameStrata("TOOLTIP") end end local function ActivateControl(widget, event, ...) --This function will call the set / execute handler for the widget --widget:GetUserDataTable() contains the needed info local user = widget:GetUserDataTable() local option = user.option local options = user.options local path = user.path local info = new() local func local group = options local funcname = GetFuncName(option) local handler local confirm local validate --build the info table containing the path -- pick up functions while traversing the tree if group[funcname] ~= nil then func = group[funcname] end handler = group.handler or handler confirm = group.confirm validate = group.validate for i = 1, #path do local v = path[i] group = GetSubOption(group, v) info[i] = v if group[funcname] ~= nil then func = group[funcname] end handler = group.handler or handler if group.confirm ~= nil then confirm = group.confirm end if group.validate ~= nil then validate = group.validate end end info.options = options info.appName = user.appName info.arg = option.arg info.handler = handler info.option = option info.type = option.type info.uiType = "dialog" info.uiName = MAJOR local name if type(option.name) == "function" then name = option.name(info) elseif type(option.name) == "string" then name = option.name else name = "" end local usage = option.usage local pattern = option.pattern local validated = true if option.type == "input" then if type(pattern)=="string" then if not strmatch(..., pattern) then validated = false end end end local success if validated and option.type ~= "execute" then if type(validate) == "string" then if handler and handler[validate] then success, validated = safecall(handler[validate], handler, info, ...) if not success then validated = false end else error(format("Method %s doesn't exist in handler for type execute", validate)) end elseif type(validate) == "function" then success, validated = safecall(validate, info, ...) if not success then validated = false end end end local rootframe = user.rootframe if type(validated) == "string" then --validate function returned a message to display if rootframe.SetStatusText then rootframe:SetStatusText(validated) else -- TODO: do something else. end PlaySound("igPlayerInviteDecline") del(info) return true elseif not validated then --validate returned false if rootframe.SetStatusText then if usage then rootframe:SetStatusText(name..": "..usage) else if pattern then rootframe:SetStatusText(name..": Expected "..pattern) else rootframe:SetStatusText(name..": Invalid Value") end end else -- TODO: do something else end PlaySound("igPlayerInviteDecline") del(info) return true else local confirmText = option.confirmText --call confirm func/method if type(confirm) == "string" then if handler and handler[confirm] then success, confirm = safecall(handler[confirm], handler, info, ...) if success and type(confirm) == "string" then confirmText = confirm confirm = true elseif not success then confirm = false end else error(format("Method %s doesn't exist in handler for type confirm", confirm)) end elseif type(confirm) == "function" then success, confirm = safecall(confirm, info, ...) if success and type(confirm) == "string" then confirmText = confirm confirm = true elseif not success then confirm = false end end --confirm if needed if type(confirm) == "boolean" then if confirm then if not confirmText then local name, desc = option.name, option.desc if type(name) == "function" then name = name(info) end if type(desc) == "function" then desc = desc(info) end confirmText = name if desc then confirmText = confirmText.." - "..desc end end local iscustom = user.rootframe:GetUserData("iscustom") local rootframe if iscustom then rootframe = user.rootframe end local basepath = user.rootframe:GetUserData("basepath") if type(func) == "string" then if handler and handler[func] then confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...) else error(format("Method %s doesn't exist in handler for type func", func)) end elseif type(func) == "function" then confirmPopup(user.appName, rootframe, basepath, info, confirmText, func, info, ...) end --func will be called and info deleted when the confirm dialog is responded to return end end --call the function if type(func) == "string" then if handler and handler[func] then safecall(handler[func],handler, info, ...) else error(format("Method %s doesn't exist in handler for type func", func)) end elseif type(func) == "function" then safecall(func,info, ...) end local iscustom = user.rootframe:GetUserData("iscustom") local basepath = user.rootframe:GetUserData("basepath") or emptyTbl --full refresh of the frame, some controls dont cause this on all events if option.type == "color" then if event == "OnValueConfirmed" then if iscustom then AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath)) else AceConfigDialog:Open(user.appName, unpack(basepath)) end end elseif option.type == "range" then if event == "OnMouseUp" then if iscustom then AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath)) else AceConfigDialog:Open(user.appName, unpack(basepath)) end end --multiselects don't cause a refresh on 'OnValueChanged' only 'OnClosed' elseif option.type == "multiselect" then user.valuechanged = true else if iscustom then AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath)) else AceConfigDialog:Open(user.appName, unpack(basepath)) end end end del(info) end local function ActivateSlider(widget, event, value) local option = widget:GetUserData("option") 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 if min then if step then value = math_floor((value - min) / step + 0.5) * step + min end value = math_max(value, min) end if max then value = math_min(value, max) end ActivateControl(widget,event,value) end --called from a checkbox that is part of an internally created multiselect group --this type is safe to refresh on activation of one control local function ActivateMultiControl(widget, event, ...) ActivateControl(widget, event, widget:GetUserData("value"), ...) local user = widget:GetUserDataTable() local iscustom = user.rootframe:GetUserData("iscustom") local basepath = user.rootframe:GetUserData("basepath") or emptyTbl if iscustom then AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath)) else AceConfigDialog:Open(user.appName, unpack(basepath)) end end local function MultiControlOnClosed(widget, event, ...) local user = widget:GetUserDataTable() if user.valuechanged then local iscustom = user.rootframe:GetUserData("iscustom") local basepath = user.rootframe:GetUserData("basepath") or emptyTbl if iscustom then AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath)) else AceConfigDialog:Open(user.appName, unpack(basepath)) end end end local function FrameOnClose(widget, event) local appName = widget:GetUserData("appName") AceConfigDialog.OpenFrames[appName] = nil gui:Release(widget) end local function CheckOptionHidden(option, options, path, appName) --check for a specific boolean option local hidden = pickfirstset(option.dialogHidden,option.guiHidden) if hidden ~= nil then return hidden end return GetOptionsMemberValue("hidden", option, options, path, appName) end local function CheckOptionDisabled(option, options, path, appName) --check for a specific boolean option local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled) if disabled ~= nil then return disabled end return GetOptionsMemberValue("disabled", option, options, path, appName) end --[[ local function BuildTabs(group, options, path, appName) local tabs = new() local text = new() local keySort = new() local opts = new() BuildSortedOptionsTable(group, keySort, opts, options, path, appName) for i = 1, #keySort do local k = keySort[i] local v = opts[k] if v.type == "group" then path[#path+1] = k local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false) local hidden = CheckOptionHidden(v, options, path, appName) if not inline and not hidden then tinsert(tabs, k) text[k] = GetOptionsMemberValue("name", v, options, path, appName) end path[#path] = nil end end del(keySort) del(opts) return tabs, text end ]] local function BuildSelect(group, options, path, appName) local groups = new() local keySort = new() local opts = new() BuildSortedOptionsTable(group, keySort, opts, options, path, appName) for i = 1, #keySort do local k = keySort[i] local v = opts[k] if v.type == "group" then path[#path+1] = k local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false) local hidden = CheckOptionHidden(v, options, path, appName) if not inline and not hidden then groups[k] = GetOptionsMemberValue("name", v, options, path, appName) end path[#path] = nil end end del(keySort) del(opts) return groups end local function BuildSubGroups(group, tree, options, path, appName) local keySort = new() local opts = new() BuildSortedOptionsTable(group, keySort, opts, options, path, appName) for i = 1, #keySort do local k = keySort[i] local v = opts[k] if v.type == "group" then path[#path+1] = k local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false) local hidden = CheckOptionHidden(v, options, path, appName) if not inline and not hidden then local entry = new() entry.value = k entry.text = GetOptionsMemberValue("name", v, options, path, appName) entry.icon = GetOptionsMemberValue("icon", v, options, path, appName) entry.iconCoords = GetOptionsMemberValue("iconCoords", v, options, path, appName) entry.disabled = CheckOptionDisabled(v, options, path, appName) if not tree.children then tree.children = new() end tinsert(tree.children,entry) if (v.childGroups or "tree") == "tree" then BuildSubGroups(v,entry, options, path, appName) end end path[#path] = nil end end del(keySort) del(opts) end local function BuildGroups(group, options, path, appName, recurse) local tree = new() local keySort = new() local opts = new() BuildSortedOptionsTable(group, keySort, opts, options, path, appName) for i = 1, #keySort do local k = keySort[i] local v = opts[k] if v.type == "group" then path[#path+1] = k local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false) local hidden = CheckOptionHidden(v, options, path, appName) if not inline and not hidden then local entry = new() entry.value = k entry.text = GetOptionsMemberValue("name", v, options, path, appName) entry.icon = GetOptionsMemberValue("icon", v, options, path, appName) entry.disabled = CheckOptionDisabled(v, options, path, appName) tinsert(tree,entry) if recurse and (v.childGroups or "tree") == "tree" then BuildSubGroups(v,entry, options, path, appName) end end path[#path] = nil end end del(keySort) del(opts) return tree end local function InjectInfo(control, options, option, path, rootframe, appName) local user = control:GetUserDataTable() for i = 1, #path do user[i] = path[i] end user.rootframe = rootframe user.option = option user.options = options user.path = copy(path) user.appName = appName control:SetCallback("OnRelease", CleanUserData) control:SetCallback("OnLeave", OptionOnMouseLeave) control:SetCallback("OnEnter", OptionOnMouseOver) end --[[ options - root of the options table being fed container - widget that controls will be placed in rootframe - Frame object the options are in path - table with the keys to get to the group being fed --]] local function FeedOptions(appName, options,container,rootframe,path,group,inline) local keySort = new() local opts = new() BuildSortedOptionsTable(group, keySort, opts, options, path, appName) for i = 1, #keySort do local k = keySort[i] local v = opts[k] tinsert(path, k) local hidden = CheckOptionHidden(v, options, path, appName) local name = GetOptionsMemberValue("name", v, options, path, appName) if not hidden then if v.type == "group" then if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then --Inline group local GroupContainer if name and name ~= "" then GroupContainer = gui:Create("InlineGroup") GroupContainer:SetTitle(name or "") else GroupContainer = gui:Create("SimpleGroup") end GroupContainer.width = "fill" GroupContainer:SetLayout("flow") container:AddChild(GroupContainer) FeedOptions(appName,options,GroupContainer,rootframe,path,v,true) end else --Control to feed local control local name = GetOptionsMemberValue("name", v, options, path, appName) if v.type == "execute" then local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName) local image, width, height = GetOptionsMemberValue("image",v, options, path, appName) if type(image) == "string" then control = gui:Create("Icon") if not width then width = GetOptionsMemberValue("imageWidth",v, options, path, appName) end if not height then height = GetOptionsMemberValue("imageHeight",v, options, path, appName) end if type(imageCoords) == "table" then control:SetImage(image, unpack(imageCoords)) else control:SetImage(image) end if type(width) ~= "number" then width = 32 end if type(height) ~= "number" then height = 32 end control:SetImageSize(width, height) control:SetLabel(name) else control = gui:Create("Button") control:SetText(name) end control:SetCallback("OnClick",ActivateControl) elseif v.type == "input" then local controlType = v.dialogControl or v.control or (v.multiline and "MultiLineEditBox") or "EditBox" control = gui:Create(controlType) if not control then geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType))) control = gui:Create(v.multiline and "MultiLineEditBox" or "EditBox") end if v.multiline and control.SetNumLines then control:SetNumLines(tonumber(v.multiline) or 4) end control:SetLabel(name) control:SetCallback("OnEnterPressed",ActivateControl) local text = GetOptionsMemberValue("get",v, options, path, appName) if type(text) ~= "string" then text = "" end control:SetText(text) elseif v.type == "toggle" then control = gui:Create("CheckBox") control:SetLabel(name) control:SetTriState(v.tristate) local value = GetOptionsMemberValue("get",v, options, path, appName) control:SetValue(value) control:SetCallback("OnValueChanged",ActivateControl) if v.descStyle == "inline" then local desc = GetOptionsMemberValue("desc", v, options, path, appName) control:SetDescription(desc) end local image = GetOptionsMemberValue("image", v, options, path, appName) local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName) if type(image) == "string" then if type(imageCoords) == "table" then control:SetImage(image, unpack(imageCoords)) else control:SetImage(image) end end elseif v.type == "range" then control = gui:Create("Slider") control:SetLabel(name) control:SetSliderValues(v.softMin or v.min or 0, v.softMax or v.max or 100, v.bigStep or v.step or 0) control:SetIsPercent(v.isPercent) local value = GetOptionsMemberValue("get",v, options, path, appName) if type(value) ~= "number" then value = 0 end control:SetValue(value) control:SetCallback("OnValueChanged",ActivateSlider) control:SetCallback("OnMouseUp",ActivateSlider) elseif v.type == "select" then local values = GetOptionsMemberValue("values", v, options, path, appName) local controlType = v.dialogControl or v.control or "Dropdown" control = gui:Create(controlType) if not control then geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType))) control = gui:Create("Dropdown") end control:SetLabel(name) control:SetList(values) local value = GetOptionsMemberValue("get",v, options, path, appName) if not values[value] then value = nil end control:SetValue(value) control:SetCallback("OnValueChanged",ActivateControl) elseif v.type == "multiselect" then local values = GetOptionsMemberValue("values", v, options, path, appName) local disabled = CheckOptionDisabled(v, options, path, appName) local controlType = v.dialogControl or v.control local valuesort = new() if values then for value, text in pairs(values) do tinsert(valuesort, value) end end tsort(valuesort) if controlType then control = gui:Create(controlType) if not control then geterrorhandler()(("Invalid Custom Control Type - %s"):format(tostring(controlType))) end end if control then control:SetMultiselect(true) control:SetLabel(name) control:SetList(values) control:SetDisabled(disabled) control:SetCallback("OnValueChanged",ActivateControl) control:SetCallback("OnClosed", MultiControlOnClosed) local width = GetOptionsMemberValue("width",v,options,path,appName) if width == "double" then control:SetWidth(width_multiplier * 2) elseif width == "half" then control:SetWidth(width_multiplier / 2) elseif width == "full" then control.width = "fill" else control:SetWidth(width_multiplier) end --check:SetTriState(v.tristate) for i = 1, #valuesort do local key = valuesort[i] local value = GetOptionsMemberValue("get",v, options, path, appName, key) control:SetItemValue(key,value) end else control = gui:Create("InlineGroup") control:SetLayout("Flow") control:SetTitle(name) control.width = "fill" control:PauseLayout() local width = GetOptionsMemberValue("width",v,options,path,appName) for i = 1, #valuesort do local value = valuesort[i] local text = values[value] local check = gui:Create("CheckBox") check:SetLabel(text) check:SetUserData("value", value) check:SetUserData("text", text) check:SetDisabled(disabled) check:SetTriState(v.tristate) check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value)) check:SetCallback("OnValueChanged",ActivateMultiControl) InjectInfo(check, options, v, path, rootframe, appName) control:AddChild(check) if width == "double" then check:SetWidth(width_multiplier * 2) elseif width == "half" then check:SetWidth(width_multiplier / 2) elseif width == "full" then check.width = "fill" else check:SetWidth(width_multiplier) end end control:ResumeLayout() control:DoLayout() end del(valuesort) elseif v.type == "color" then control = gui:Create("ColorPicker") control:SetLabel(name) control:SetHasAlpha(v.hasAlpha) control:SetColor(GetOptionsMemberValue("get",v, options, path, appName)) control:SetCallback("OnValueChanged",ActivateControl) control:SetCallback("OnValueConfirmed",ActivateControl) elseif v.type == "keybinding" then control = gui:Create("Keybinding") control:SetLabel(name) control:SetKey(GetOptionsMemberValue("get",v, options, path, appName)) control:SetCallback("OnKeyChanged",ActivateControl) elseif v.type == "header" then control = gui:Create("Heading") control:SetText(name) control.width = "fill" elseif v.type == "description" then control = gui:Create("Label") control:SetText(name) local fontSize = GetOptionsMemberValue("fontSize",v, options, path, appName) if fontSize == "medium" then control:SetFontObject(GameFontHighlight) elseif fontSize == "large" then control:SetFontObject(GameFontHighlightLarge) else -- small or invalid control:SetFontObject(GameFontHighlightSmall) end local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName) local image, width, height = GetOptionsMemberValue("image",v, options, path, appName) if type(image) == "string" then if not width then width = GetOptionsMemberValue("imageWidth",v, options, path, appName) end if not height then height = GetOptionsMemberValue("imageHeight",v, options, path, appName) end if type(imageCoords) == "table" then control:SetImage(image, unpack(imageCoords)) else control:SetImage(image) end if type(width) ~= "number" then width = 32 end if type(height) ~= "number" then height = 32 end control:SetImageSize(width, height) end local width = GetOptionsMemberValue("width",v,options,path,appName) control.width = not width and "fill" end --Common Init if control then if control.width ~= "fill" then local width = GetOptionsMemberValue("width",v,options,path,appName) if width == "double" then control:SetWidth(width_multiplier * 2) elseif width == "half" then control:SetWidth(width_multiplier / 2) elseif width == "full" then control.width = "fill" else control:SetWidth(width_multiplier) end end if control.SetDisabled then local disabled = CheckOptionDisabled(v, options, path, appName) control:SetDisabled(disabled) end InjectInfo(control, options, v, path, rootframe, appName) container:AddChild(control) end end end tremove(path) end container:ResumeLayout() container:DoLayout() del(keySort) del(opts) end local function BuildPath(path, ...) for i = 1, select("#",...) do tinsert(path, (select(i,...))) end end local function TreeOnButtonEnter(widget, event, uniquevalue, button) local user = widget:GetUserDataTable() if not user then return end local options = user.options local option = user.option local path = user.path local appName = user.appName local feedpath = new() for i = 1, #path do feedpath[i] = path[i] end BuildPath(feedpath, ("\001"):split(uniquevalue)) local group = options for i = 1, #feedpath do if not group then return end group = GetSubOption(group, feedpath[i]) end local name = GetOptionsMemberValue("name", group, options, feedpath, appName) local desc = GetOptionsMemberValue("desc", group, options, feedpath, appName) GameTooltip:SetOwner(button, "ANCHOR_NONE") if widget.type == "TabGroup" then GameTooltip:SetPoint("BOTTOM",button,"TOP") else GameTooltip:SetPoint("LEFT",button,"RIGHT") end GameTooltip:SetText(name, 1, .82, 0, 1) if type(desc) == "string" then GameTooltip:AddLine(desc, 1, 1, 1, 1) end GameTooltip:Show() end local function TreeOnButtonLeave(widget, event, value, button) GameTooltip:Hide() end local function GroupExists(appName, options, path, uniquevalue) if not uniquevalue then return false end local feedpath = new() local temppath = new() for i = 1, #path do feedpath[i] = path[i] end BuildPath(feedpath, ("\001"):split(uniquevalue)) local group = options for i = 1, #feedpath do local v = feedpath[i] temppath[i] = v group = GetSubOption(group, v) if not group or group.type ~= "group" or CheckOptionHidden(group, options, temppath, appName) then del(feedpath) del(temppath) return false end end del(feedpath) del(temppath) return true end local function GroupSelected(widget, event, uniquevalue) local user = widget:GetUserDataTable() local options = user.options local option = user.option local path = user.path local rootframe = user.rootframe local feedpath = new() for i = 1, #path do feedpath[i] = path[i] end BuildPath(feedpath, ("\001"):split(uniquevalue)) local group = options for i = 1, #feedpath do group = GetSubOption(group, feedpath[i]) end widget:ReleaseChildren() AceConfigDialog:FeedGroup(user.appName,options,widget,rootframe,feedpath) del(feedpath) end --[[ -- INTERNAL -- This function will feed one group, and any inline child groups into the given container Select Groups will only have the selection control (tree, tabs, dropdown) fed in and have a group selected, this event will trigger the feeding of child groups Rules: If the group is Inline, FeedOptions If the group has no child groups, FeedOptions If the group is a tab or select group, FeedOptions then add the Group Control If the group is a tree group FeedOptions then its parent isnt a tree group: then add the tree control containing this and all child tree groups if its parent is a tree group, its already a node on a tree --]] function AceConfigDialog:FeedGroup(appName,options,container,rootframe,path, isRoot) local group = options --follow the path to get to the curent group local inline local grouptype, parenttype = options.childGroups, "none" for i = 1, #path do local v = path[i] group = GetSubOption(group, v) inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) parenttype = grouptype grouptype = group.childGroups end if not parenttype then parenttype = "tree" end --check if the group has child groups local hasChildGroups for k, v in pairs(group.args) do if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then hasChildGroups = true end end if group.plugins then for plugin, t in pairs(group.plugins) do for k, v in pairs(t) do if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) and not CheckOptionHidden(v, options, path, appName) then hasChildGroups = true end end end end container:SetLayout("flow") local scroll --Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and (parenttype == "tree" and not isRoot)) then if container.type ~= "InlineGroup" and container.type ~= "SimpleGroup" then scroll = gui:Create("ScrollFrame") scroll:SetLayout("flow") scroll.width = "fill" scroll.height = "fill" container:SetLayout("fill") container:AddChild(scroll) container = scroll end end FeedOptions(appName,options,container,rootframe,path,group,nil) if scroll then container:PerformLayout() local status = self:GetStatusTable(appName, path) if not status.scroll then status.scroll = {} end scroll:SetStatusTable(status.scroll) end if hasChildGroups and not inline then local name = GetOptionsMemberValue("name", group, options, path, appName) if grouptype == "tab" then local tab = gui:Create("TabGroup") InjectInfo(tab, options, group, path, rootframe, appName) tab:SetCallback("OnGroupSelected", GroupSelected) tab:SetCallback("OnTabEnter", TreeOnButtonEnter) tab:SetCallback("OnTabLeave", TreeOnButtonLeave) local status = AceConfigDialog:GetStatusTable(appName, path) if not status.groups then status.groups = {} end tab:SetStatusTable(status.groups) tab.width = "fill" tab.height = "fill" local tabs = BuildGroups(group, options, path, appName) tab:SetTabs(tabs) tab:SetUserData("tablist", tabs) for i = 1, #tabs do local entry = tabs[i] if not entry.disabled then tab:SelectTab((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value) break end end container:AddChild(tab) elseif grouptype == "select" then local select = gui:Create("DropdownGroup") select:SetTitle(name) InjectInfo(select, options, group, path, rootframe, appName) select:SetCallback("OnGroupSelected", GroupSelected) local status = AceConfigDialog:GetStatusTable(appName, path) if not status.groups then status.groups = {} end select:SetStatusTable(status.groups) local grouplist = BuildSelect(group, options, path, appName) select:SetGroupList(grouplist) select:SetUserData("grouplist", grouplist) local firstgroup for k, v in pairs(grouplist) do if not firstgroup or k < firstgroup then firstgroup = k end end if firstgroup then select:SetGroup((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or firstgroup) end select.width = "fill" select.height = "fill" container:AddChild(select) --assume tree group by default --if parenttype is tree then this group is already a node on that tree elseif (parenttype ~= "tree") or isRoot then local tree = gui:Create("TreeGroup") InjectInfo(tree, options, group, path, rootframe, appName) tree:EnableButtonTooltips(false) tree.width = "fill" tree.height = "fill" tree:SetCallback("OnGroupSelected", GroupSelected) tree:SetCallback("OnButtonEnter", TreeOnButtonEnter) tree:SetCallback("OnButtonLeave", TreeOnButtonLeave) local status = AceConfigDialog:GetStatusTable(appName, path) if not status.groups then status.groups = {} end local treedefinition = BuildGroups(group, options, path, appName, true) tree:SetStatusTable(status.groups) tree:SetTree(treedefinition) tree:SetUserData("tree",treedefinition) for i = 1, #treedefinition do local entry = treedefinition[i] if not entry.disabled then tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value) break end end container:AddChild(tree) end end end local old_CloseSpecialWindows local function RefreshOnUpdate(this) for appName in pairs(this.closing) do if AceConfigDialog.OpenFrames[appName] then AceConfigDialog.OpenFrames[appName]:Hide() end if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do if not widget:IsVisible() then widget:ReleaseChildren() end end end this.closing[appName] = nil end if this.closeAll then for k, v in pairs(AceConfigDialog.OpenFrames) do if not this.closeAllOverride[k] then v:Hide() end end this.closeAll = nil wipe(this.closeAllOverride) end for appName in pairs(this.apps) do if AceConfigDialog.OpenFrames[appName] then local user = AceConfigDialog.OpenFrames[appName]:GetUserDataTable() AceConfigDialog:Open(appName, unpack(user.basepath or emptyTbl)) end if AceConfigDialog.BlizOptions and AceConfigDialog.BlizOptions[appName] then for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do local user = widget:GetUserDataTable() if widget:IsVisible() then AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(user.basepath or emptyTbl)) end end end this.apps[appName] = nil end this:SetScript("OnUpdate", nil) end -- Upgrade the OnUpdate script as well, if needed. if AceConfigDialog.frame:GetScript("OnUpdate") then AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate) end --- Close all open options windows function AceConfigDialog:CloseAll() AceConfigDialog.frame.closeAll = true AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate) if next(self.OpenFrames) then return true end end --- Close a specific options window. -- @param appName The application name as given to `:RegisterOptionsTable()` function AceConfigDialog:Close(appName) if self.OpenFrames[appName] then AceConfigDialog.frame.closing[appName] = true AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate) return true end end -- Internal -- Called by AceConfigRegistry function AceConfigDialog:ConfigTableChanged(event, appName) AceConfigDialog.frame.apps[appName] = true AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate) end reg.RegisterCallback(AceConfigDialog, "ConfigTableChange", "ConfigTableChanged") --- Sets the default size of the options window for a specific application. -- @param appName The application name as given to `:RegisterOptionsTable()` -- @param width The default width -- @param height The default height function AceConfigDialog:SetDefaultSize(appName, width, height) local status = AceConfigDialog:GetStatusTable(appName) if type(width) == "number" and type(height) == "number" then status.width = width status.height = height end end --- Open an option window at the specified path (if any). -- This function can optionally feed the group into a pre-created container -- instead of creating a new container frame. -- @paramsig appName [, container][, ...] -- @param appName The application name as given to `:RegisterOptionsTable()` -- @param container An optional container frame to feed the options into -- @param ... The path to open after creating the options window (see `:SelectGroup` for details) function AceConfigDialog:Open(appName, container, ...) if not old_CloseSpecialWindows then old_CloseSpecialWindows = CloseSpecialWindows CloseSpecialWindows = function() local found = old_CloseSpecialWindows() return self:CloseAll() or found end end local app = reg:GetOptionsTable(appName) if not app then error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2) end local options = app("dialog", MAJOR) local f local path = new() local name = GetOptionsMemberValue("name", options, options, path, appName) --If an optional path is specified add it to the path table before feeding the options --as container is optional as well it may contain the first element of the path if type(container) == "string" then tinsert(path, container) container = nil end for n = 1, select("#",...) do tinsert(path, (select(n, ...))) end --if a container is given feed into that if container then f = container f:ReleaseChildren() f:SetUserData("appName", appName) f:SetUserData("iscustom", true) if #path > 0 then f:SetUserData("basepath", copy(path)) end local status = AceConfigDialog:GetStatusTable(appName) if not status.width then status.width = 700 end if not status.height then status.height = 500 end if f.SetStatusTable then f:SetStatusTable(status) end if f.SetTitle then f:SetTitle(name or "") end else if not self.OpenFrames[appName] then f = gui:Create("Frame") self.OpenFrames[appName] = f else f = self.OpenFrames[appName] end f:ReleaseChildren() f:SetCallback("OnClose", FrameOnClose) f:SetUserData("appName", appName) if #path > 0 then f:SetUserData("basepath", copy(path)) end f:SetTitle(name or "") local status = AceConfigDialog:GetStatusTable(appName) f:SetStatusTable(status) end self:FeedGroup(appName,options,f,f,path,true) if f.Show then f:Show() end del(path) if AceConfigDialog.frame.closeAll then -- close all is set, but thats not good, since we're just opening here, so force it AceConfigDialog.frame.closeAllOverride[appName] = true end end -- convert pre-39 BlizOptions structure to the new format if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then local old = AceConfigDialog.BlizOptions local new = {} for key, widget in pairs(old) do local appName = widget:GetUserData("appName") if not new[appName] then new[appName] = {} end new[appName][key] = widget end AceConfigDialog.BlizOptions = new else AceConfigDialog.BlizOptions = AceConfigDialog.BlizOptions or {} end local function FeedToBlizPanel(widget, event) local path = widget:GetUserData("path") AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl)) end local function ClearBlizPanel(widget, event) local appName = widget:GetUserData("appName") AceConfigDialog.frame.closing[appName] = true AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate) end --- Add an option table into the Blizzard Interface Options panel. -- You can optionally supply a descriptive name to use and a parent frame to use, -- as well as a path in the options table.\\ -- If no name is specified, the appName will be used instead. -- -- If you specify a proper `parent` (by name), the interface options will generate a -- tree layout. Note that only one level of children is supported, so the parent always -- has to be a head-level note. -- -- This function returns a reference to the container frame registered with the Interface -- Options. You can use this reference to open the options with the API function -- `InterfaceOptionsFrame_OpenToCategory`. -- @param appName The application name as given to `:RegisterOptionsTable()` -- @param name A descriptive name to display in the options tree (defaults to appName) -- @param parent The parent to use in the interface options tree. -- @param ... The path in the options table to feed into the interface options panel. -- @return The reference to the frame registered into the Interface Options. function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...) local BlizOptions = AceConfigDialog.BlizOptions local key = appName for n = 1, select("#", ...) do key = key.."\001"..select(n, ...) end if not BlizOptions[appName] then BlizOptions[appName] = {} end if not BlizOptions[appName][key] then local group = gui:Create("BlizOptionsGroup") BlizOptions[appName][key] = group group:SetName(name or appName, parent) group:SetTitle(name or appName) group:SetUserData("appName", appName) if select("#", ...) > 0 then local path = {} for n = 1, select("#",...) do tinsert(path, (select(n, ...))) end group:SetUserData("path", path) end group:SetCallback("OnShow", FeedToBlizPanel) group:SetCallback("OnHide", ClearBlizPanel) InterfaceOptions_AddCategory(group.frame) return group.frame else error(("%s has already been added to the Blizzard Options Window with the given path"):format(appName), 2) end end
