changeset 17:b9897e874fac v1.3.6

merge tag
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Fri, 03 Aug 2012 03:40:12 -0400
parents 3a2beea01a28 (diff) bb135ad3065c (current diff)
children 23fbc8ea967e
files .hgtags
diffstat 14 files changed, 1008 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Fri Aug 03 07:35:59 2012 +0000
+++ b/.hgtags	Fri Aug 03 03:40:12 2012 -0400
@@ -1,1 +1,2 @@
-0000000000000000000000000000000000000000 v1.3.5
+4f0a29493035fd8907919015d21f9145d3e5920a beta3
+4b870c06a6c8f9a8e4822bbff61b97983ca7186a beta4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.pkgmeta	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,34 @@
+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
+
+  # for Ace3 stuff, it's usually better to grab the alphas (they get tested)
+  # than waiting for bugfixes to get tagged
+  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/AceLocale-3.0:
+    url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceLocale-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 Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,252 @@
+--[[-----------------------------------------------------------------------------
+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
+Version 4 restore original frame methods, as fortold by ancient prophecy
+Version 5 don't bogart the widget object
+-farmbuyer
+-------------------------------------------------------------------------------]]
+local Type, Version = "lib-st", 4
+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 CreateST 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.customSetPoint = rawget(self.frame,"SetPoint")
+		self.frame.realSetPoint = self.frame.SetPoint
+		self.frame.SetPoint = ShiftingSetPoint
+		self.frame.SetAllPoints = ShiftingSetAllPoints
+	--end
+
+	-- This needs the .frame field.  This also unconditionally creates .obj
+	-- inside that field and calls a SetScript on it 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
+		self.frame.realSetPoint = nil
+		self.frame.SetAllPoints = nil
+		self.frame.SetPoint = self.frame.customSetPoint
+		self.frame.customSetPoint = nil
+	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 Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,37 @@
+## Interface: 40300
+## Title: Which Rank Does What
+## Version: @project-version@
+## Notes: Displays a grid of guild ranks versus permissions.
+## Author: Farmbuyer of US-Kilrogg
+## SavedVariables: wrdwDB
+## OptionalDeps: Ace3, lib-st, LibFarmbuyer
+## LoadOnDemand: 1
+## LoadWith: Blizzard_GuildUI, Blizzard_GuildControlUI, Blizzard_GuildBankUI
+
+#@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\AceLocale-3.0\AceLocale-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@
+
+locale-enUS.lua
+locale-deDE.lua
+locale-esES.lua
+locale-esMX.lua
+locale-frFR.lua
+locale-koKR.lua
+locale-ruRU.lua
+locale-zhCN.lua
+locale-zhTW.lua
+
+AceGUIWidget-lib-st.lua
+main.lua
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-deDE.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "deDE")
+if not L then return end
+
+--@localization(locale="deDE", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-enUS.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,7 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "enUS", --[[default=]]true, --[[silent=]]true)
+
+--@localization(locale="enUS", format="lua_additive_table", same-key-is-true=true)@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-esES.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "esES")
+if not L then return end
+
+--@localization(locale="esES", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-esMX.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "esMX")
+if not L then return end
+
+--@localization(locale="esMX", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-frFR.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "frFR")
+if not L then return end
+
+--@localization(locale="frFR", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-koKR.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "koKR")
+if not L then return end
+
+--@localization(locale="koKR", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-ruRU.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "ruRU")
+if not L then return end
+
+--@localization(locale="ruRU", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-zhCN.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "zhCN")
+if not L then return end
+
+--@localization(locale="zhCN", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/locale-zhTW.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,8 @@
+-- Which Rank Does What Locale
+-- Please use the Localization App on WoWAce to Update this
+-- http://www.wowace.com/addons/whichrankdoeswhat/localization/
+local nametag = ...
+local L = LibStub("AceLocale-3.0"):NewLocale(nametag, "zhTW")
+if not L then return end
+
+--@localization(locale="zhTW", format="lua_additive_table", handle-unlocalized="comment")@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.lua	Fri Aug 03 03:40:12 2012 -0400
@@ -0,0 +1,612 @@
+local nametag, addon = ...
+local L = LibStub("AceLocale-3.0"):GetLocale(nametag)
+
+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 = L["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 = L["Use this addon"],
+					type = 'toggle',
+					arg  = "ToggleEnable",
+					order = 5,
+				},
+				guildcontrol = {
+					name = L["Guild Control for non-GMs"],
+					desc = L["Make the grayed-out Guild Control button activate this addon instead."],
+					type = 'toggle',
+					width = 'double',   -- else it gets cut off
+					order = 10,
+				},
+				break1 = {
+					name = '',
+					type = 'description',
+					cmdHidden = true,
+					width = 'full',
+					order = 14,
+				},
+				popup = {
+					name = "/wrdw",
+					desc = L["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 st_colwidth          = 80 --65
+local cols_per_group       = 6
+local num_flagsets
+local sidetabs
+local incomplete
+local flagmap
+
+-- Remove children ST widgets without explicitly Release()'ing them.  As there
+-- are no children other than STs, no "normal" widget resources are leaked.
+local function DisownChildren (container)
+	for i,v in ipairs(container.children) do
+		container.children[i] = nil
+		v.frame:Hide()
+		v.frame:ClearAllPoints()
+	end
+end
+
+local function setstatus(txt)
+	addon.display:SetStatusText(txt)
+	addon.tooltip = #txt > 40 and txt or nil
+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", "OnSlashCommand")
+
+	-- Ideally, most of this stuff wouldn't be done at load time at all; this
+	-- whole addon should be LoD.
+	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()
+		if not NUM_RANK_FLAGS then
+			UIParentLoadAddOn("Blizzard_GuildControlUI")
+		end
+		local function noreallyitsokay()
+			GuildControlButton:Enable()
+			GuildControlButton:SetScript("OnClick", onclick)
+			GuildControlButton:SetScript("OnEnter", onenter)
+		end
+		hooksecurefunc("GuildInfoFrame_UpdatePermissions", noreallyitsokay)
+		-- This doesn't seem to be used anymore...?
+		hooksecurefunc("GuildFrame_CheckPermissions", noreallyitsokay)
+	end
+end
+
+function addon:OnDisable()
+	self:Print(L["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:OnSlashCommand (input)
+	if not NUM_RANK_FLAGS then  -- in case a GM didn't get it loaded earlier
+		GuildFrame_LoadUI()
+		UIParentLoadAddOn("Blizzard_GuildControlUI")
+	end
+	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
+do
+	local text = '|cffff1010' .. L["Guild flags have changed!"] .. '|r  ' ..
+	             L["You must close and reopen this window to display the changes."]
+	function addon:GUILD_RANKS_UPDATE()
+		self.perms = nil
+		if (not incomplete) and self.display and self.display:IsVisible() then
+			setstatus(text)
+		end
+	end
+end
+
+
+function addon:BuildPerms()
+	assert(UIParentLoadAddOn("Blizzard_GuildControlUI"))
+	local check = "|TInterface\\Buttons\\UI-CheckBox-Check:"..(st_rowheight+5).."|t"
+
+	-- http://www.wowace.com/addons/lib-st/pages/set-data/minimal-dataset-format/
+	local p,v = {}, {}
+	flagmap = {}
+	num_flagsets = math.floor(NUM_RANK_FLAGS/cols_per_group + 1)
+	for flagset = 1, num_flagsets do
+		p[flagset] = {}
+	end
+
+	for r = 1, GuildControlGetNumRanks() do
+		GuildControlSetRank(r)
+
+		-- permissions (most special handling goes here)
+		-- flag 14 is no longer used
+		-- flags 15 and 16 may have numeric values not just a boolean
+		-- flag 21 is... not included yet, apparently
+		local flags = { GuildControlGetRankFlags() }
+		for flagset = 1, num_flagsets do
+			local row = { GuildControlGetRankName(r) }
+			for c_offset = 1, cols_per_group do while true do
+				local c = (flagset-1) * cols_per_group + c_offset
+				--if c == 14 or c > NUM_RANK_FLAGS then break end
+				if c > NUM_RANK_FLAGS then break end
+				local newcol = #row + 1
+				flagmap[flagset..'x'..newcol] = c
+				if c == 15 or c == 16 then
+					local val = GetGuildBankWithdrawGoldLimit()
+					row[newcol] = flags[c] and ((val == -1) and check or val) or ""
+				elseif c == 14 then
+					row[newcol] = ""
+				else
+					row[newcol] = flags[c] and check or ""
+				end
+				break
+			end end
+			p[flagset][r] = row
+		end
+
+		-- guild vault
+		local banktabs = {}
+		for t = 1, GetNumGuildBankTabs() do
+			-- isViewable, canDeposit, editText, numWithdrawals 
+			banktabs[t] = { GuildControlGetRankName(r), 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 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])
+			local buttons = self.display:GetUserData("extra buttons")
+			buttons['prev']:Disable()
+			buttons['next']:Disable()
+		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(L["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
+
+
+-- The "closebutton" variable isn't accessible through the widget.  I should
+-- probably just constuct an entire Frame-equse thing by hand instead...  gah.
+local function FIXFRAME (container, ...)
+	for i = 1, select('#',...) do
+		local child = select(i,...)
+		if child:GetObjectType() == "Button" and child:GetText() == CLOSE then
+			container:SetUserData("close button", child)
+			return
+		end
+	end
+end
+
+local function adjust_flagset (button)
+	local key = button.button_key
+	assert (key == 'prev' or key == 'next')
+	local flagset = addon.current_main_st
+	if key == 'prev' then
+		flagset = flagset - 1
+	else
+		flagset = flagset + 1
+	end
+	return flagset
+end
+
+local function AddedButton_OnEnter (button)
+	-- not very generic, that's okay until we need to be
+	local high = adjust_flagset(button) * cols_per_group
+	local low = high - cols_per_group + 1
+	if high > NUM_RANK_FLAGS then high = NUM_RANK_FLAGS end
+	setstatus(L["Show flag columns %d - %d"]:format(low,high))
+end
+local function AddedButton_OnLeave (button)
+	setstatus("")
+end
+local function AddedButton_OnClick (button)
+	addon:DoMainST(adjust_flagset(button))
+	-- the buttons may have changed state, adjust their status text
+	if button:IsEnabled() then
+		AddedButton_OnEnter (button)
+	else
+		AddedButton_OnLeave (button)
+	end
+end
+
+local function AddButton (container, key, label)
+	assert(not tonumber(key))
+	local all = container:GetUserData("extra buttons") or {}
+	container:SetUserData("extra buttons", all)
+	local n = #all
+	local closebutton = assert(container:GetUserData("close button"), "something horrible")
+	local b = CreateFrame("Button", nil, container.frame, "UIPanelButtonTemplate")
+	b.button_key = key
+	b.obj = self
+	b:SetScript("OnClick", AddedButton_OnClick)
+	b:SetScript("OnEnter", AddedButton_OnEnter)
+	b:SetScript("OnLeave", AddedButton_OnLeave)
+	b:SetText(label)
+	b:SetHeight(20)
+	b:SetWidth(50)    -- "Close" is 100
+	b:SetPoint("BOTTOMRIGHT", closebutton, "BOTTOMLEFT", -5, 0)
+	b:Show()
+	all[n+1] = b
+	all[key] = b
+
+	for i = n, 1, -1 do
+		local ob = all[i]
+		ob:ClearAllPoints()
+		ob:SetPoint("BOTTOMRIGHT", all[i+1], "BOTTOMLEFT", -5, 0)
+	end
+	-- The Frame's statusbar is not accessible via the Frame widget itself.
+	-- Which is nice and properly encapsulated and all, but also inconvenient.
+	-- We'll take the long route there.
+	local sb = container.statustext:GetParent()
+	assert (sb.obj == container)
+	sb:ClearAllPoints()
+	sb:SetPoint("BOTTOMLEFT", 15, 15)   -- default
+	sb:SetPoint("BOTTOMRIGHT", all[1], "BOTTOMLEFT", -5, 0)
+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
+
+
+local function OnEnterStatusBar (container)
+	if not addon.tooltip then return end
+	GameTooltip:SetOwner (container.frame, "ANCHOR_RIGHT")
+	GameTooltip:ClearLines()
+	GameTooltip:AddLine (nametag)
+	GameTooltip:AddLine (addon.tooltip, 0.8, 0.8, 0.8, 1)
+	GameTooltip:Show()
+end
+
+
+function addon:BuildMainSTs (permissions, parent_frame)
+	local errtxt = "flagset %d, column %d, failed to map to a flag number"
+	-- if this language uses a trailing colon, strip it
+	local ranklabel = GUILDCONTROL_RANKLABEL:gsub(":$","")
+	self.main_sts = {}
+
+	for flagset = 1, #permissions do
+		local cols = {{
+			name = ranklabel,
+			width = 10 * #ranklabel,
+		}}
+		for c = #cols+1, #permissions[flagset][1] do  -- all ranks work here
+			local f = flagmap[flagset..'x'..c]
+			if not f then error(errtxt:format(flagset, c)) end
+			table.insert(cols,{
+				-- the only special handling outside BuildPerms
+				name = _G[f == 14 and 'UNUSED' or 'GUILDCONTROL_OPTION'..f],
+				width = st_colwidth,
+			})
+		end
+
+		local ST = LibStub("ScrollingTable"):CreateST (cols, st_displayed_rows, st_rowheight, --[[highlight=]]nil, parent_frame)
+		ST:Hide()
+
+		ST:SetData(permissions[flagset], --[[minimal format=]]true)
+		ST:RegisterEvents{
+			OnEnter = st_OnEnter,
+			OnLeave = st_OnLeave,
+			OnClick = st_OnClick,
+			OnDoubleClick = st_OnClick,
+		}
+		self.main_sts[flagset] = ST
+	end
+end
+
+function addon:BuildVaultSTs (permissions, parent_frame)
+	self.vault_sts = {}
+	local cols = {
+		self.main_sts[1].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, --[[highlight=]]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:DoMainST (index)
+	self.current_main_st = index
+	DisownChildren(self.display)
+	self.display:AddChild(self.main_sts[index])
+	local buttons = self.display:GetUserData("extra buttons")
+	buttons['prev'][index <= 1 and 'Disable' or 'Enable'](buttons['prev'])
+	buttons['next'][index >= num_flagsets and 'Disable' or 'Enable'](buttons['next'])
+end
+
+
+-- Under normal conditions, this massive wodge is built once, and then merely
+-- :Show'n and :Hide'n.  Only if info gets out of date do we release/destroy
+-- the UI elements and rebuild.
+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:EnableResize(false)
+		self.display:SetStatusTable{
+			width = (st_colwidth+4) * cols_per_group      -- flag columns
+			        + 105,                                -- rank label column
+			height = 500,
+		}
+		self.display:ApplyStatus()
+		FIXFRAME (self.display, self.display.frame:GetChildren())
+		AddButton (self.display, 'prev', "<<")
+		AddButton (self.display, 'next', ">>")
+		self.display:SetCallback("OnEnterStatusBar", OnEnterStatusBar)
+		self.display:SetCallback("OnLeaveStatusBar", GameTooltip_Hide)
+		self.display:SetCallback("OnClose", function(_d)
+			if incomplete or (not self.perms) then
+				-- stuff changed while open
+				self.perms = nil
+				self.display = nil
+				if sidetabs then for i,s in ipairs(sidetabs) do
+					s:Hide()
+					s:ClearAllPoints()
+					s:SetParent(nil)   -- Blizzard does this too.  Huh.
+				end end
+				sidetabs = nil
+				for i,b in ipairs(_d:GetUserData("extra buttons")) do
+					b.obj = nil
+					b:Hide()
+					b:ClearAllPoints()
+					b:SetParent(nil)
+				end
+				AceGUI:Release(_d)
+			end
+		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 sidetabs then for i,s in ipairs(sidetabs) do
+			s:Hide()
+			s:ClearAllPoints()
+			s:SetParent(nil)   -- Blizzard does this too.  Huh.
+		end end
+		if self.main_sts then for i = 1, #self.main_sts do
+			if self.main_sts[i] and self.main_sts[i].st then
+				self.main_sts[i]:Release()
+			end
+		end 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_sts = nil
+		self.vault_sts = nil
+	end
+	if not self.main_sts then
+		self:BuildMainSTs (self.perms, self.display.content)
+		for i,st in ipairs(self.main_sts) do
+			self.main_sts[i] = AceGUI:Create("lib-st"):WrapST(st)
+			self.main_sts[i].head_offset = 20
+		end
+		self:DoMainST(1)
+
+		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)
+			self:DoMainST(self.current_main_st)
+		end)
+		maintab.tooltip = [[Rank permissions]]
+		maintab:SetChecked(true)
+		self:BuildVaultTabs()
+	end
+
+	self.display:Show()
+	return self.display
+end
+
+-- vim:noet