Mercurial > wow > whichrankdoeswhat
changeset 1:17a4945d14eb
Initial functioning checkin.
| author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
|---|---|
| date | Fri, 14 Jan 2011 00:48:17 +0000 |
| parents | de6232dda772 |
| children | 78ff21480511 |
| files | .pkgmeta AceGUIWidget-lib-st.lua WhichRankDoesWhat.toc main.lua |
| diffstat | 4 files changed, 734 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.pkgmeta Fri Jan 14 00:48:17 2011 +0000 @@ -0,0 +1,28 @@ +package-as: WhichRankDoesWhat + +externals: + libs/LibStub: + url: svn://svn.wowace.com/wow/libstub/mainline/trunk + tag: latest + libs/CallbackHandler-1.0: + url: svn://svn.wowace.com/wow/callbackhandler/mainline/trunk/CallbackHandler-1.0 + tag: latest + libs/AceAddon-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceAddon-3.0 + libs/AceConfig-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConfig-3.0 + libs/AceEvent-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceEvent-3.0 + libs/AceConsole-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConsole-3.0 + libs/AceDBOptions-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDBOptions-3.0 + libs/AceDB-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDB-3.0 + libs/AceGUI-3.0: + url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceGUI-3.0 + libs/lib-st: + url: svn://svn.wowace.com/wow/lib-st/mainline/trunk + tag: latest + +# vim: et
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AceGUIWidget-lib-st.lua Fri Jan 14 00:48:17 2011 +0000 @@ -0,0 +1,249 @@ +--[[----------------------------------------------------------------------------- +lib-st Beta Wrapper Widget + +lib-st does not recycle the objects (called "ST" here) that it creates and +returns. We therefore do not try to hold onto an ST when the widget is +being recycled. This means that Constructor() does very little work, and +does not actually construct an ST. + +OnAcquire cannot construct an ST either, because we don't yet have any +creation parameters from the user to feed to CreateST. (Allowing such to +be passed along from AceGUI:Create() would require changes to core AceGUI +code, and I don't feel like trying to overcome that inertia.) + +The upshot is that the widget returned from Create is broken and useless +until its CreateST member has been called. This means that correct behavior +depends entirely on the user remembering to do so. + +"The gods do not protect fools. Fools are protected by more capable fools." +- Ringworld + + +Version 1 initial functioning implementation +Version 2 reshuffle to follow new AceGUI widget coding style +Version 3 add .tail_offset, defaulting to same absolute value as .head_offset +-farmbuyer +-------------------------------------------------------------------------------]] +local Type, Version = "lib-st", 3 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local ipairs, error = ipairs, error + +-- WoW APIs +local debugstack = debugstack +local CreateFrame = CreateFrame + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +-- Some AceGUI functions simply Won't Work in this context. Name them +-- here, and code calling them will get a somewhat informative error(). +local oopsfuncs = { + 'SetRelativeWidth', 'SetRelativeHeight', + 'SetFullWidth', 'SetFullHeight', +} +local err = "Oops! The AceGUI function you tried to call (%s) does not " + .. "make sense with lib-st and has not been implemented." + +local function Oops(self) + -- if you ever wanted an example of "brown paper bag" code, here it is + local ds = debugstack(0) + local func = ds:match("AceGUIWidget%-lib%-st%.lua:%d+:%s+in function `(%a+)'") + error(err:format(func or "?")) +end + + +--[[ + Users with an ST already constructed can drop it into a widget directly + using this routine. It must be safe to call this more than once with + new widgets on the same ST. + + This is where most of the intelligence of the wrapper is located. That + is, if you can call my code "intelligent" with a straight face. Lemme + try again. + + Think of the widget wrapper as a brain. When ALL THREE neurons manage + to fire at the same time and produce a thought, this function represents + the thought. Sigh. +]] +local ShiftingSetPoint, ShiftingSetAllPoints +local function WrapST (self, st) + if not st.frame then + error"lib-st instance has no '.frame' field... wtf did you pass to this function?" + end + if st.frame.obj and (st.frame.obj ~= self) then + error"lib-st instance already has an '.obj' field from a different widget, cannot use with AceGUI!" + end + self.st = st + if not st.head then + error"lib-st instance has no '.head' field, must use either ScrollingTable:CreateST or this widget's CreatST first" + end + self.frame = st.frame -- gutsy, but looks doable + + -- Possibly have already wrapped this ST in a previous widget, careful. + if st.frame.obj ~= self then + self.frame.realSetPoint = self.frame.SetPoint + self.frame.SetPoint = ShiftingSetPoint + self.frame.SetAllPoints = ShiftingSetAllPoints + end + + -- This needs the .frame field. This also creates .obj inside that + -- field and calls a SetScript as well. + return AceGUI:RegisterAsWidget(self) +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +--[[ + All of an ST's subframes are attached to its main frame, which we have in + the st.frame link, and that's what AceGUI uses for all positioning. Except + that ST:SetDisplayCols creates its "head" row /above/ the main frame, and + so the row of labels eats into whatever upper border space AceGUI calculates, + often overlapping other elements. + + We get around this by replacing ST's main frame's SetPoint with a custom + version that just moves everything down a few pixels to allow room for the + head row. + + FIXME this may need to be a secure hook (ugh, would end up calling the real + setpoint twice) rather than a replacement. +]] +local DEFAULT_OFFSET = 7 +function ShiftingSetPoint(frame,anchor,other,otheranchor,xoff,yoff) + local ho,to = frame.obj.head_offset, frame.obj.tail_offset + yoff = yoff or 0 + if anchor:sub(1,3) == "TOP" then + yoff = yoff - ho + elseif anchor:sub(1,6) == "BOTTOM" then + yoff = yoff + to + end + return frame.realSetPoint(frame,anchor,other,otheranchor,xoff,yoff) +end +function ShiftingSetAllPoints(frame,other) + ShiftingSetPoint(frame,"TOPLEFT",other,"TOPLEFT",0,0) + ShiftingSetPoint(frame,"BOTTOMRIGHT",other,"BOTTOMRIGHT",0,0) +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + -- -------------------------------------------------------------- + -- These are expected by AceGUI containers (and AceGUI users) + -- + ["OnAcquire"] = function (self) + -- Almost nothing can usefully be done here. + self.head_offset = DEFAULT_OFFSET + self.tail_offset = DEFAULT_OFFSET + end, + + ["OnRelease"] = function (self) + if self.st then + self.st.frame:ClearAllPoints() + self.st:Hide() + end + self.st = nil + -- XXX should also undo the frame hooks. would most likely be wasted + -- cycles. if somebody actually wants to make an ST, include it inside + -- an ace container, then hide the container and continue displaying + -- the ST by other means, they can file a ticket + end, + + --[[ + STs don't use a "normal" SetWidth, if we define "normal" to be the + behavior of the blizzard :SetWidth. Column width is passed in during + creation of the whole ST. The SetWidth defined by an ST takes no + arguments; "ReCalculateWidth" would be a more precise description of + what it does. + + Parts of AceGUI look for a .width field because a widget's SetWidth + sets such. ST calculates a total width and dispatches it to its member + frame... but doesn't store a local copy. We need to bridge these + differences. + + This widget wrapper does not make use of On{Width,Height}Set hooks, + but the acegui widget base functions do. Since we're not inheriting + them, we may as well supply them. + ]] + ["SetWidth"] = function (self) + self.st:SetWidth() -- re-total the columns + local w = self.st.frame:GetWidth() -- fetch the answer back + self.frame.width = w -- store it for acegui + if self.OnWidthSet then + self:OnWidthSet(w) + end + end, + + -- Everything said about SetWidth applies here too. + ["SetHeight"] = function (self) + self.st:SetHeight() + local h = self.st.frame:GetHeight() + self.frame.height = h + if self.OnHeightSet then + self:OnHeightSet(h) + end + end, + + -- Some of the container layouts call Show/Hide on the innermost frame + -- directly. We need to make sure the slightly-higher-level routine is + -- also called. + ["LayoutFinished"] = function (self) + if self.frame:IsShown() then + self.st:Show() + else + self.st:Hide() + end + end, + + -- -------------------------------------------------------------- + -- Functions specific to this widget + -- + + ["GetSTLibrary"] = function (self) -- Purely for convenience + return LibST + end, + + --[[ + Replacement wrapper, so that instead of + st = ScrollingTable:CreateST( args ) + the user should be able to do + st = AceGUI:Create("lib-st"):CreateST( args ) + instead, without needing to get a lib-st handle. + ]] + ["CreateST"] = function (self, ...) + return self:WrapST( LibST:CreateST(...) ) + end, + + ["WrapST"] = WrapST, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] +local function Constructor() + -- .frame not done here, see WrapST + local widget = { + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + + for _,func in ipairs(oopsfuncs) do + widget[func] = Oops + end + + -- AceGUI:RegisterAsWidget needs .frame + return widget +end + +AceGUI:RegisterWidgetType(Type,Constructor,Version) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WhichRankDoesWhat.toc Fri Jan 14 00:48:17 2011 +0000 @@ -0,0 +1,24 @@ +## Interface: 40000 +## Title: Which Rank Does What +## Version: @project-version@ +## Notes: Displays a grid of guild ranks versus permissions. +## Author: Farmbuyer of Kilrogg +## SavedVariables: wrdwDB +## OptionalDeps: Ace3, lib-st, LibFarmbuyer + +#@no-lib-strip@ +libs\LibStub\LibStub.lua +libs\CallbackHandler-1.0\CallbackHandler-1.0.xml +libs\AceAddon-3.0\AceAddon-3.0.xml +libs\AceDB-3.0\AceDB-3.0.xml +libs\AceDBOptions-3.0\AceDBOptions-3.0.xml +libs\AceEvent-3.0\AceEvent-3.0.xml +libs\AceConsole-3.0\AceConsole-3.0.xml +libs\AceGUI-3.0\AceGUI-3.0.xml +libs\AceConfig-3.0\AceConfig-3.0.xml +libs\lib-st\lib-st.xml +#@end-no-lib-strip@ + +AceGUIWidget-lib-st.lua +main.lua +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.lua Fri Jan 14 00:48:17 2011 +0000 @@ -0,0 +1,433 @@ +local nametag, addon = ... + +addon.defaults = { + profile = { + enable = true, + guildcontrol = true, + }, +} + + +addon.options = { + name = "", + type = 'group', + childGroups = 'tab', + handler = addon, -- functions listed as strings called as addon:func + get = "GetOption", + set = "SetOption", + args = { + general = { + name = "General", + desc = "General options", + type = 'group', + order = 10, + args = { + version = { + --name = filled in during OnInit + type = 'description', + fontSize = "large", + cmdHidden = true, + width = 'full', + order = 1, + }, + enable = { + name = "Enable", + desc = "Use this addon", + type = 'toggle', + arg = "ToggleEnable", + order = 5, + }, + guildcontrol = { + name = "Guild Control for non-GMs", + desc = [[Make the grayed-out Guild Control button activate this addon instead.]], + type = 'toggle', + order = 10, + }, + break1 = { + name = '', + type = 'description', + cmdHidden = true, + width = 'full', + order = 14, + }, + popup = { + name = "/wrdw", + desc = "Toggle WRDW window", + type = 'execute', + func = function() + InterfaceOptionsFrameCancel:Click() + HideUIPanel(GameMenuFrame) + addon:BuildWindow() + end, + order = 15, + }, + }, + }, + --profiles = filled in OnInit + }, +} + + +----------------------------------------------------------------------------- +-- other locals +local AceGUI = LibStub("AceGUI-3.0") +local st_rowheight = 25 +local st_displayed_rows = 15 --math.floor(366/st_rowheight) +local sidetabs +local incomplete + +-- Remove children widgets without explicitly Release()'ing them. +local function DisownChildren (container) + for i,v in ipairs(container.children) do + container.children[i] = nil + v.frame:Hide() + v.frame:ClearAllPoints() + end +end + + +----------------------------------------------------------------------------- +addon = LibStub("AceAddon-3.0"):NewAddon(addon, nametag, + "AceConsole-3.0", "AceEvent-3.0") + +-- Thanks to jerry for the nifty arg idea. +function addon:SetOption (info, value) + local name = info[#info] + self.db.profile[name] = value + local arg = info.arg + if arg then self[arg](self) end +end + +function addon:GetOption (info) + local name = info[#info] + return self.db.profile[name] +end + +function addon:OnInitialize() + self.db = LibStub("AceDB-3.0"):New("wrdwDB", self.defaults, --[[Default=]]true) + + local AceDBOptions = LibStub("AceDBOptions-3.0", true) + if AceDBOptions then + self.options.args.profiles = AceDBOptions:GetOptionsTable(self.db) + self.options.args.profiles.order = 200 + end + + self.options.args.general.args.version.name = + "|cff30adffVersion " .. (GetAddOnMetadata(nametag, "Version") or "?") .. "|r" + LibStub("AceConfig-3.0"):RegisterOptionsTable(nametag, self.options) + self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions(nametag, "WhichRankDoesWhat") + --self.optionsFrame.okay = function() pattern_editing_safe = false end + --self.optionsFrame.refresh = self.optionsFrame.okay + --self.optionsFrame.cancel = self.optionsFrame.okay + + self:SetEnabledState(self.db.profile.enable) + self.OnInitialize = nil +end + + +function addon:OnEnable() + self:RegisterEvent("GUILD_RANKS_UPDATE") + self:RegisterChatCommand("wrdw", "OnChatCommand") + + if (not IsGuildLeader()) and self.db.profile.guildcontrol then + local function onclick() addon:BuildWindow() end + local function onenter(this) GameTooltip_AddNewbieTip(this, GUILDCONTROL, 1.0, 1.0, 1.0, "/wrdw", 1) end + GuildFrame_LoadUI() + hooksecurefunc("GuildFrame_CheckPermissions", function() + GuildControlButton:Enable() + GuildControlButton:SetScript("OnClick", onclick) + GuildControlButton:SetScript("OnEnter", onenter) + end) + end +end + +function addon:OnDisable() + self:Print([[You will need to relog or /reload to fully disable this addon.]]) +end + +function addon:ToggleEnable() + if self.db.profile.enable then + self:Enable() + else + self:Disable() + end +end + +function addon:OnChatCommand (input) + if not input or input:trim() == "" then + if self.display and self.display:IsShown() then + self.display:Hide() + else + self:BuildWindow() + end + else + --LibStub("AceConfigCmd-3.0").HandleCommand(self, "wrdw", nametag, input) + LibStub("AceConfigDialog-3.0"):Open(nametag) + end +end + + +-- Something somewhere has changed, redo the cache +function addon:GUILD_RANKS_UPDATE() + self.perms = nil + if (not incomplete) and self.display and self.display:IsVisible() then + self.display:SetStatusText([[|cffff1010Guild flags have changed!|r You must close and reopen this window to display the changes.]]) + end +end + + +function addon:BuildPerms() + assert(UIParentLoadAddOn("Blizzard_GuildControlUI")) + local check = "|TInterface\\Buttons\\UI-CheckBox-Check:"..(st_rowheight-2).."|t" + + -- http://www.wowace.com/addons/lib-st/pages/set-data/minimal-dataset-format/ + local p,v = {}, {} + for r = 1, GuildControlGetNumRanks() do + GuildControlSetRank(r) + + -- permissions + local flags = { GuildControlGetRankFlags() } + local row = { GuildControlGetRankName(r) } + for c = 1, NUM_RANK_FLAGS do if c ~= 14 then + local newcol = #row + 1 + if c == 15 or c == 16 then + local val = GetGuildBankWithdrawGoldLimit() + row[newcol] = flags[c] and ((val == -1) and check or val) or "" + else + row[newcol] = flags[c] and check or "" + end + end end + p[r] = row + + -- guild vault + local banktabs = {} + for t = 1, GetNumGuildBankTabs() do + -- isViewable, canDeposit, editText, numWithdrawals + banktabs[t] = { row[1], GetGuildBankTabPermissions(t) } + banktabs[t][2] = banktabs[t][2] and check or "" + banktabs[t][3] = banktabs[t][3] and check or "" + banktabs[t][4] = banktabs[t][4] and check or "" + local withdraw = banktabs[t][5] + banktabs[t][5] = (withdraw == -1) and check or (withdraw == 0) and "" or withdraw + end + v[r] = banktabs + end + self.perms = p + -- This one needs to be turned inside-out to match the data requirements + self.vault = {} + for t = 1, GetNumGuildBankTabs() do + self.vault[t] = {} + for r = 1, #v do + self.vault[t][r] = v[r][t] + end + end +end + + +local function setstatus(txt) addon.display:SetStatusText(txt) end + +local make_sidetab +do + local lastclicked + local function OnClick (thistab) + if thistab == lastclicked then return end + for i = 1, #sidetabs do + sidetabs[i]:SetChecked(false) + end + thistab:SetChecked(true) -- should be redundant, but just in case + lastclicked = thistab + if thistab.callback then + thistab:callback(thistab:GetID()) + end + end + + -- Some magic numbers here wrt the index + function make_sidetab (index, callback) + if not sidetabs then + sidetabs = {} + end + + local tab = CreateFrame("CheckButton", "WRDWTab"..index, addon.display.frame, "SpellBookSkillLineTabTemplate", index) + if index > 1 then + tab:SetPoint("TOPLEFT", sidetabs[index-1], "BOTTOMLEFT", 0, -17) + else + tab:SetNormalTexture("Interface\\SpellBook\\GuildSpellbooktabBG") + tab.TabardEmblem:Show() + tab.TabardIconFrame:Show() + SetLargeGuildTabardTextures("player", tab.TabardEmblem, tab:GetNormalTexture(), tab.TabardIconFrame) + tab:SetPoint("TOPLEFT", addon.display.frame, "TOPRIGHT", 0, -17) + end + tab:SetScript("OnClick", OnClick) + --tab:SetChecked(false) -- is default + tab:Show() + tab.callback = callback + sidetabs[index] = tab + return tab + end + + function addon:BuildVaultTabs() + incomplete = nil + local offset = 1 -- number of tabs already made + local function pick_a_tab (tab, id) + DisownChildren(self.display) + self.display:AddChild(self.vault_sts[id-offset]) + end + for t = 1, GetNumGuildBankTabs() do + local name, icon = GetGuildBankTabInfo(t) + incomplete = incomplete or icon == [[Interface\Icons\INV_Misc_QuestionMark]] + local tab = make_sidetab(t+offset, pick_a_tab) + tab:SetNormalTexture(icon) + tab.tooltip = name + end + if incomplete then + setstatus[[Guild vault information is incomplete. Be closer to a vault, and give it some time. You may need to relog and/or open the guild roster/vault to force a client update.]] + end + end +end + + +local function st_OnEnter (rowFrame, cellFrame, data, cols, row, realrow, column, sttable, button, ...) + if (row == nil) or (realrow == nil) then -- mouseover column header + setstatus(cellFrame:GetText():gsub('\n',' ')) + return true + end + return false -- continue with default highlighting behavior +end +local function st_OnLeave (rowFrame, cellFrame, data, cols, row, realrow, column, sttable, button, ...) + setstatus("") + return false -- continue with default un-highlighting behavior +end +local function st_OnClick (rowFrame, cellFrame, data, cols, row, realrow, column, sttable, button, ...) + if (row == nil) or (realrow == nil) then return true end -- click column header, suppress reordering + -- more here? + return true -- do not do anything further +end + + +function addon:BuildMainST (permissions, parent_frame) + -- if this language uses a trailing colon, strip it + local tmp = GUILDCONTROL_RANKLABEL:gsub(":$","") + local cols = {{ + name = tmp, + width = 10 * #tmp, + }} + for i = 1, NUM_RANK_FLAGS do if i ~= 14 then + table.insert(cols,{ + name = _G['GUILDCONTROL_OPTION'..i], + width = 62, + }) + end end + + local ST = LibStub("ScrollingTable"):CreateST (cols, st_displayed_rows, st_rowheight, nil, parent_frame) + ST:Hide() + + ST:SetData(permissions, --[[minimal format=]]true) + ST:RegisterEvents{ + OnEnter = st_OnEnter, + OnLeave = st_OnLeave, + OnClick = st_OnClick, + OnDoubleClick = st_OnClick, + } + + return ST +end + +function addon:BuildVaultSTs (permissions, parent_frame) + self.vault_sts = {} + local cols = { + self.main_st.st.cols[1], + { name = GUILDCONTROL_VIEW_TAB, width = 80 }, + { name = GUILDCONTROL_DEPOSIT_ITEMS, width = 80 }, + { name = GUILDCONTROL_UPDATE_TEXT, width = 80 }, + { name = GUILDCONTROL_WITHDRAW_ITEMS, width = 150 }, + } + + for tab = 1, #permissions do + local ST = LibStub("ScrollingTable"):CreateST (cols, st_displayed_rows, st_rowheight, nil, parent_frame) + ST:Hide() + ST:SetData(permissions[tab], --[[minimal format=]]true) + ST:RegisterEvents{ + OnEnter = st_OnEnter, + OnLeave = st_OnLeave, + OnClick = st_OnClick, + OnDoubleClick = st_OnClick, + } + self.vault_sts[tab] = ST + end +end + + +function addon:BuildWindow() + local need_tabs + if self.display then + self.display:Hide() + else + self.display = AceGUI:Create("Frame") + self.display:SetTitle("Which Rank Does What") + self.display:SetLayout("Fill") + self.display:SetStatusTable{ + width = 1225, + height = 500, + } + self.display:ApplyStatus() + self.display:SetCallback("OnClose", function(_d) + if incomplete or (not self.perms) then + -- stuff changed while open + self.perms = nil + self.display = nil + sidetabs = nil + AceGUI:Release(_d) + end + end) + if self.display.EnableResize then + self.display:EnableResize(false) + end + need_tabs = true + end + + if not self.perms then + need_tabs = true + self:BuildPerms() -- creates self.perms and self.vault + DisownChildren(self.display) + -- Could be new rows, fewer rows, changed tickboxes... ugh, trying to + -- update the scrolltable is a pain. Throw it out and start over. + if self.main_st and self.main_st.st then + self.main_st:Release() + end + if self.vault_sts then for i = 1, #self.vault_sts do + if self.vault_sts[i] and self.vault_sts[i].st then + self.vault_sts[i]:Release() + end + end end + self.main_st = nil + self.vault_sts = nil + end + if not self.main_st then + local st = self:BuildMainST (self.perms, self.display.content) + self.main_st = AceGUI:Create("lib-st"):WrapST(st) + self.main_st.head_offset = 20 + --st_widget.tail_offset = 5 + self.display:AddChild(self.main_st) + + self:BuildVaultSTs (self.vault, self.display.content) + for i,st in ipairs(self.vault_sts) do + self.vault_sts[i] = AceGUI:Create("lib-st"):WrapST(st) + self.vault_sts[i].head_offset = 20 + end + end + + if need_tabs or incomplete then + local maintab = make_sidetab(1, function (this, id) + DisownChildren(self.display) + self.display:AddChild(self.main_st) + end) + maintab.tooltip = [[Rank permissions]] + maintab:SetChecked(true) + self:BuildVaultTabs() + end + + self.display:Show() + return self.display +end + +-- vim:noet
